package mylittleinvestigations;

import mylittleinvestigations.CaseInformation.CaseXmlWriter;
import mylittleinvestigations.CaseInformation.Case;
import mylittleinvestigations.CaseInformation.CaseXmlReader;
import mylittleinvestigations.CaseInformation.CaseParseException;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import mylittleinvestigations.EasingFunctions.*;
import mylittleinvestigations.Events.DialogEventProvider;
import mylittleinvestigations.Events.DialogEventListener;
import mylittleinvestigations.FieldCharacters.FieldCharacter;
import mylittleinvestigations.FieldCharacters.FieldCharacterDirection;
import mylittleinvestigations.FieldCharacters.FieldCharacterState;
import org.apache.commons.codec.digest.DigestUtils;
import org.dawnmew.jpurewave.SoundSystem;
import org.newdawn.slick.*;

public class Conversation
{
    protected ArrayList<Action> actionList;
    protected ContinuousAction currentContinuousAction;
    private ContinuousAction lastContinuousAction;
    private String initialBgmId;
    private String initialAmbianceSfxId;

    public Conversation()
    {
        this.actionList = new ArrayList<Action>();
        this.state = new State();
        this.isEnabled = false;
        this.setHasBeenCompleted(false);
    }

    protected String id;

    public String getId() { return this.id; }
    public void setId(String id) { this.id = id; }

    protected String name;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    protected String unlockToId;

    public String getUnlockToId() { return this.unlockToId; }
    public void setUnlockToId(String unlockToId) { this.unlockToId = unlockToId; }

    protected String requiredPartnerId;

    public String getRequiredPartnerId() { return this.requiredPartnerId; }
    public void setRequiredPartnerId(String requiredPartnerId) { this.requiredPartnerId = requiredPartnerId; }

    protected boolean isEnabled;

    public boolean getIsEnabled() { return this.isEnabled; }
    public void setIsEnabled(boolean isEnabled) { this.isEnabled = isEnabled; }
    
    private ArrayList<String> unlockConditions;
    
    public ArrayList<String> getUnlockConditions() { return this.unlockConditions; }
    
    private ArrayList<String> clearedUnlockConditions;
    
    public ArrayList<String> getClearedUnlockConditions() { return this.clearedUnlockConditions; }
    
    public boolean getIsLocked()
    {
        return !this.unlockConditions.isEmpty();
    }
    
    public boolean getHasBeenUnlocked()
    {
        if (!this.getIsLocked())
        {
            return false;
        }
        
        boolean hasBeenUnlocked = true;
        
        for (String unlockCondition : this.unlockConditions)
        {
            if (this.clearedUnlockConditions.contains(unlockCondition))
            {
                hasBeenUnlocked = false;
                break;
            }
        }
        
        return hasBeenUnlocked;
    }

    private boolean hasBeenCompleted;

    public boolean getHasBeenCompleted() { return this.hasBeenCompleted; }
    public void setHasBeenCompleted(boolean hasBeenCompleted) { this.hasBeenCompleted = hasBeenCompleted; }

    private boolean wrongPartnerUsed;

    public boolean getWrongPartnerUsed() { return this.wrongPartnerUsed; }
    public void setWrongPartnerUsed(boolean wrongPartnerUsed) { this.wrongPartnerUsed = wrongPartnerUsed; }
    
    private boolean isFinished;

    public boolean getIsFinished() { return this.isFinished; }
    public void setIsFinished(boolean isFinished) { this.isFinished = isFinished; }
    
    private State state;

    public State getState() { return this.state; }
    private void setState(State state) { this.state = state; }

    public void Begin(State state)
    {
        this.Reset();
        this.state = state;
        this.initialBgmId = SoundSystem.getInstance().getPlayingMusic();
        //this.initialAmbianceId = SoundSystem.getInstance().getPlayingAmbiance();

        this.setWrongPartnerUsed(
            this.getRequiredPartnerId() != null &&
            !Utils.equals(this.getRequiredPartnerId(), Case.getInstance().getPartnerManager().GetCurrentPartnerId()));
        
        state.setCurrentConversation(this);
    }

    public void Update(GameContainer gc, int delta) throws CaseParseException
    {
        if (this.getIsFinished())
        {
            if (this.lastContinuousAction != null)
            {
                this.lastContinuousAction.Update(gc,delta);
            }

            return;
        }

        if (this.currentContinuousAction == null || (this.currentContinuousAction.getIsFinished() && this.currentContinuousAction.getIsReadyToHide()))
        {
            if (this.currentContinuousAction != null && this.currentContinuousAction.getIsFinished())
            {
                this.lastContinuousAction = this.currentContinuousAction;
                this.currentContinuousAction = null;

                if (!this.state.getActionIndexSet())
                {
                    this.state.setActionIndex(this.state.getActionIndex() + 1);
                }

                this.state.ClearActionIndexSet();
            }

            while (this.state.getActionIndex() < this.actionList.size())
            {
                Action currentAction = this.actionList.get(this.state.getActionIndex());

                if (currentAction.getIsSingleAction())
                {
                    SingleAction singleAction = (SingleAction)currentAction;
                    singleAction.Execute(this.state);
                }
                else
                {
                    this.currentContinuousAction = (ContinuousAction)currentAction;

                    if (this.currentContinuousAction.getShouldSkip())
                    {
                        this.currentContinuousAction.GoToNext(this.state);
                    }
                    else
                    {
                        if (this.lastContinuousAction != null)
                        {
                            this.lastContinuousAction.Reset();
                        }
                        
                        this.currentContinuousAction.Begin(this.state);

                        if (!this.currentContinuousAction.getIsFinished())
                        {
                            break;
                        }
                    }
                }

                if (this.state.getEndRequested())
                {
                    this.setIsFinished(true);
                    Case.getInstance().getAudioManager().PlayBgmWithId(this.initialBgmId);
                    //Case.getInstance().getAudioManager().PlayAmbianceWithId(this.initialAmbianceSfxId);
                    return;
                }

                if (!this.state.getActionIndexSet())
                {
                    this.state.setActionIndex(this.state.getActionIndex() + 1);
                }

                this.state.ClearActionIndexSet();
            }
        }

        if (this.state.getActionIndex() == this.actionList.size() || this.state.getEndRequested())
        {
            if (this.state.getEndRequested())
            {
                this.state.setEndRequested(false);
            }
            else if (!this.getWrongPartnerUsed())
            {
                this.setHasBeenCompleted(true);
            }

            if (!this.state.getPreserveBgm())
            {
                Case.getInstance().getAudioManager().PlayBgmWithId(this.initialBgmId);
            }
            
            //if (!this.state.getPreserveAmbiance())
            //{
            //    Case.getInstance().getAudioManager().PlayAmbianceWithId(this.initialAmbianceSfxId);
            //}

            if (this.currentContinuousAction != null)
            {
                this.currentContinuousAction.Reset();
            }

            this.setIsFinished(true);
        }
        else if (this.currentContinuousAction != null)
        {
            this.currentContinuousAction.Update(gc,delta);
        }
    }

    public void Draw(GameContainer gc, Graphics g, float yOffset) throws SlickException
    {
        if (this.getIsFinished())
        {
            if (this.lastContinuousAction != null)
            {
                this.lastContinuousAction.Draw(gc, g, yOffset);
            }

            return;
        }

        if (this.currentContinuousAction != null)
        {
            this.currentContinuousAction.Draw(gc, g, yOffset);
        }
    }

    public void Reset()
    {
        this.state.ResetForConversation();
        this.state = null;

        if (this.lastContinuousAction != null)
        {
            this.lastContinuousAction.Reset();
        }
        
        this.currentContinuousAction = null;
        this.setIsFinished(false);
    }

    public void ChangeCharacter(CharacterPosition position, String characterIdToChangeTo, String initialEmotionId, String sfxId)
    {
        if (initialEmotionId == null)
        {
            initialEmotionId = "Neutral";
        }

        MultipleCharacterChangeAction multipleCharacterChange = null;
        CharacterChangeAction characterChange = new CharacterChangeAction(position, characterIdToChangeTo, initialEmotionId, sfxId);
        
        if (sfxId != null)
        {
            Case.getInstance().getAudioManager().RequestSfx(sfxId);
        }

        // If the previous action was a character change action,
        // we want all of those to occur simultaneously.
        if (this.actionList.size() > 0 && (multipleCharacterChange = Utils.as(this.actionList.get(this.actionList.size() - 1), MultipleCharacterChangeAction.class)) != null)
        {
            multipleCharacterChange.AddCharacterChange(characterChange);
        }
        else
        {
            multipleCharacterChange = new MultipleCharacterChangeAction();
            multipleCharacterChange.AddCharacterChange(characterChange);
            this.actionList.add(multipleCharacterChange);
        }
    }

    public void ShowDialog(CharacterPosition speakerPosition, String characterId, String rawDialog, String filePath, int leadInTime) throws CaseParseException
    {
        this.actionList.add(new ShowDialogAction(speakerPosition, characterId, rawDialog, filePath, leadInTime));
    }

    public void ShowDialogAutomatic(CharacterPosition speakerPosition, String characterId, String rawDialog, String filePath, int leadInTime, int delayBeforeContinuing) throws CaseParseException
    {
        this.actionList.add(new ShowDialogAutomaticAction(speakerPosition, characterId, rawDialog, filePath, leadInTime, delayBeforeContinuing));
    }

    MustPresentEvidenceAction currentMustPresentEvidenceAction = null;
    
    public void MustPresentEvidence(CharacterPosition speakerPosition, String characterId, String rawDialog, String filePath, int leadInTime) throws CaseParseException
    {
        this.currentMustPresentEvidenceAction = new MustPresentEvidenceAction(speakerPosition, characterId, rawDialog, filePath, leadInTime);
        this.actionList.add(this.currentMustPresentEvidenceAction);
    }
    
    public void AddCorrectEvidenceId(String correctEvidenceId) throws CaseParseException
    {
        if (this.currentMustPresentEvidenceAction == null)
        {
            throw new CaseParseException("Correct evidence can only be attached to a MustPresentEvidence action.");
        }
        
        this.currentMustPresentEvidenceAction.AddCorrectEvidenceId(correctEvidenceId);
    }

    public void BeginWrongEvidencePresented()
    {
        this.actionList.add(new SkipWrongEvidencePresentedAction());
        this.actionList.add(new BeginWrongEvidencePresentedAction());
    }

    public void EndWrongEvidencePresented()
    {
        this.actionList.add(new EndWrongEvidencePresentedAction());
    }

    public void BeginEndRequested()
    {
        this.actionList.add(new SkipEndRequestedAction());
        this.actionList.add(new BeginEndRequestedAction());
    }

    public void EndEndRequested()
    {
        this.actionList.add(new EndEndRequestedAction());
    }

    public void EndMustPresentEvidence() throws CaseParseException
    {
        int index = this.actionList.size();
        BeginWrongEvidencePresentedAction beginWrongEvidencePresentedAction = null;
        int beginWrongEvidencePresentedActionIndex = -1;
        BeginEndRequestedAction beginEndRequestedAction = null;
        int beginEndRequestedActionIndex = -1;
        MustPresentEvidenceAction mustPresentEvidenceAction = null;

        for (int i = index - 1; i >= 0 && mustPresentEvidenceAction == null; i--)
        {
            Action action = this.actionList.get(i);

            if (beginWrongEvidencePresentedAction == null)
            {
                beginWrongEvidencePresentedAction = Utils.as(action, BeginWrongEvidencePresentedAction.class);
                beginWrongEvidencePresentedActionIndex = i;
            }

            if (beginEndRequestedAction == null)
            {
                beginEndRequestedAction = Utils.as(action, BeginEndRequestedAction.class);
                beginEndRequestedActionIndex = i;
            }

            if (mustPresentEvidenceAction == null)
            {
                mustPresentEvidenceAction = Utils.as(action, MustPresentEvidenceAction.class);
            }
        }

        if (mustPresentEvidenceAction != null)
        {
            mustPresentEvidenceAction.setCorrectEvidencePresentedIndex(index);

            if (beginWrongEvidencePresentedAction != null)
            {
                mustPresentEvidenceAction.setWrongEvidencePresentedIndex(beginWrongEvidencePresentedActionIndex);
            }
            else
            {
                throw new CaseParseException("Cannot have MustPresentEvidence without BeginWrongEvidencePresented appearing within it.");
            }

            if (beginEndRequestedAction != null)
            {
                mustPresentEvidenceAction.setEndRequestedIndex(beginEndRequestedActionIndex);
            }
            else
            {
                mustPresentEvidenceAction.setEndRequestedIndex(-1);
            }
        }
        else
        {
            throw new CaseParseException("Cannot have EndMustPresentEvidence without MustPresentEvidence appearing previously.");
        }

        this.actionList.add(new EndMustPresentEvidenceAction());
        this.currentMustPresentEvidenceAction = null;
    }

    public void EnableConversation(String conversationId, boolean notify)
    {
        this.actionList.add(new EnableConversationAction(conversationId));

        NotificationAction notificationAction = new NotificationAction();

        if (notify)
        {
            this.actionList.add(notificationAction);
        }
        
        Case.getInstance().AddEnableConversationDependencyCheck(conversationId, notificationAction);
    }

    public void EnableEvidence(String evidenceId, boolean notify)
    {
        this.actionList.add(new EnableEvidenceAction(evidenceId));

        NotificationAction notificationAction = new NotificationAction(null, evidenceId);

        if (notify)
        {
            this.actionList.add(notificationAction);
        }
        
        Case.getInstance().AddEnableEvidenceDependencyCheck(evidenceId, notificationAction);
    }

    public void UpdateEvidence(String evidenceId, String newEvidenceId, boolean notify)
    {
        this.actionList.add(new UpdateEvidenceAction(evidenceId, newEvidenceId));

        NotificationAction notificationAction = new NotificationAction(evidenceId, newEvidenceId);

        if (notify)
        {
            this.actionList.add(notificationAction);
        }
        
        Case.getInstance().AddUpdateEvidenceDependencyCheck(evidenceId, newEvidenceId, notificationAction);
    }

    public void DisableEvidence(String evidenceId, boolean notify)
    {
        this.actionList.add(new DisableEvidenceAction(evidenceId));

        NotificationAction notificationAction = new NotificationAction(null, evidenceId);

        if (notify)
        {
            this.actionList.add(notificationAction);
        }
        
        Case.getInstance().AddDisableEvidenceDependencyCheck(evidenceId, notificationAction);
    }
    
    public void EnableCutscene(String cutsceneId)
    {
        this.actionList.add(new EnableCutsceneAction(cutsceneId));
    }

    public void SetFlag(String flagId, String notificationString)
    {
        this.actionList.add(new SetFlagAction(flagId));
        
        if (notificationString != null && !notificationString.isEmpty())
        {
            NotificationAction notificationAction = new NotificationAction();
            notificationAction.setRawNotificationText(notificationString);
            this.actionList.add(notificationAction);
        }
    }

    public void BeginBranchOnCondition(Condition condition)
    {
        this.actionList.add(new BranchOnConditionAction(condition));
    }

    public void BranchIfTrue()
    {
        this.actionList.add(new BranchIfTrueAction());
    }

    public void BranchIfFalse()
    {
        this.actionList.add(new BranchIfFalseAction());
    }

    public void EndBranchOnCondition()
    {
        int stackLevel = 0;
        BranchOnConditionAction branchOnConditionAction = null;
        BranchIfTrueAction branchIfTrueAction = null;
        int branchIfTrueIndex = this.actionList.size();
        BranchIfFalseAction branchIfFalseAction = null;
        int branchIfFalseIndex = this.actionList.size();

        for (int i = this.actionList.size() - 1; branchOnConditionAction == null && i >= 0; i--)
        {
            Action curAction = this.actionList.get(i);

            if (EndBranchOnConditionAction.class.isInstance(curAction))
            {
                stackLevel++;
            }
            else if (stackLevel > 0)
            {
                if (BranchOnConditionAction.class.isInstance(curAction))
                {
                    stackLevel--;
                }
            }
            else
            {
                if (branchOnConditionAction == null)
                {
                    branchOnConditionAction = Utils.as(curAction, BranchOnConditionAction.class);
                }

                if (branchIfTrueAction == null)
                {
                    branchIfTrueAction = Utils.as(curAction, BranchIfTrueAction.class);

                    if (branchIfTrueAction != null)
                    {
                        branchIfTrueIndex = i;
                    }
                }

                if (branchIfFalseAction == null)
                {
                    branchIfFalseAction = Utils.as(curAction, BranchIfFalseAction.class);

                    if (branchIfFalseAction != null)
                    {
                        branchIfFalseIndex = i;
                    }
                }
            }
        }

        // Only do anything if there actually was a previous accompanying branch-on-flag action.
        if (branchOnConditionAction != null)
        {
            int index = this.actionList.size();

            // +1 since we want to skip past the actual branching action and onto the contents of the branch.
            branchOnConditionAction.setTrueIndex(branchIfTrueIndex + 1);
            branchOnConditionAction.setFalseIndex(branchIfFalseIndex + 1);

            if (branchIfTrueAction != null)
            {
                branchIfTrueAction.setEndIndex(index);
            }

            if (branchIfFalseAction != null)
            {
                branchIfFalseAction.setEndIndex(index);
            }

            this.actionList.add(new EndBranchOnConditionAction());
        }
    }

    public void PlayBgm(String bgmId)
    {
        this.actionList.add(new PlayBgmAction(bgmId, false /* preserveBgm */));
    }

    public void PlayBgm(String bgmId, boolean preserveBgm)
    {
        this.actionList.add(new PlayBgmAction(bgmId, preserveBgm));
    }

    public void PauseBgm()
    {
        this.actionList.add(new PauseBgmAction());
    }

    public void ResumeBgm()
    {
        this.actionList.add(new ResumeBgmAction());
    }

    public void StopBgm(boolean isInstant)
    {
        this.actionList.add(new StopBgmAction(isInstant));
    }

    public void StopBgm(boolean isInstant, boolean preserveBgm)
    {
        this.actionList.add(new StopBgmAction(isInstant, preserveBgm));
    }

    public void PlayAmbiance(String ambianceSfxId)
    {
        this.actionList.add(new PlayAmbianceAction(ambianceSfxId, false /* preserveAmbiance */));
    }

    public void PlayAmbiance(String ambianceSfxId, boolean preserveAmbiance)
    {
        this.actionList.add(new PlayAmbianceAction(ambianceSfxId, preserveAmbiance));
    }

    public void PauseAmbiance()
    {
        this.actionList.add(new PauseAmbianceAction());
    }

    public void ResumeAmbiance()
    {
        this.actionList.add(new ResumeAmbianceAction());
    }

    public void StopAmbiance(boolean isInstant)
    {
        this.actionList.add(new StopAmbianceAction(isInstant));
    }

    public void StopAmbiance(boolean isInstant, boolean preserveAmbiance)
    {
        this.actionList.add(new StopAmbianceAction(isInstant, preserveAmbiance));
    }

    public void StartAnimation(String id)
    {
        this.actionList.add(new StartAnimationAction(id));
    }

    public void StopAnimation()
    {
        this.actionList.add(new StopAnimationAction());
    }

    public void SetPartner(String partnerId, boolean notify)
    {
        this.actionList.add(new SetPartnerAction(partnerId));

        NotificationAction notificationAction = new NotificationAction(partnerId);

        if (notify)
        {
            this.actionList.add(notificationAction);
        }
        
        Case.getInstance().AddSetPartnerDependencyCheck(partnerId, notificationAction);
    }

    public void GoToPresentWrongEvidence()
    {
        this.actionList.add(new GoToPresentWrongEvidenceAction());
    }

    public void LockConversation(String lockCondition, String lockConditionParameter) throws CaseParseException
    {
        this.actionList.add(new LockConversationAction(lockCondition, lockConditionParameter));
    }

    public void ExitEncounter()
    {
        this.actionList.add(new ExitEncounterAction());
    }

    public void MoveToLocation(String newAreaId, String newLocationId, String transitionId)
    {
        this.actionList.add(new MoveToLocationAction(newAreaId, newLocationId, transitionId));
        this.actionList.add(new ExitEncounterAction());
    }

    public void MoveToZoomedView(String zoomedViewId)
    {
        this.actionList.add(new MoveToZoomedViewAction(zoomedViewId));
        this.actionList.add(new ExitEncounterAction());
    }

    public void EndCase()
    {
        this.actionList.add(new EndCaseAction(false /* completesCase */));
    }

    public void CompleteCase()
    {
        this.actionList.add(new EndCaseAction(true /* completesCase */));
    }
    
    private BeginMultipleChoiceAction currentBeginMultipleChoiceAction;
    private int currentBeginMultipleChoiceActionIndex;
    private ArrayList<ExitMultipleChoiceAction> exitMultipleChoiceActions = new ArrayList<ExitMultipleChoiceAction>();

    public void BeginMultipleChoice() throws CaseParseException
    {
        if (this.currentBeginMultipleChoiceAction != null)
        {
            throw new CaseParseException("Cannot have nested BeginMultipleChoices.");
        }
        
        this.currentBeginMultipleChoiceAction = new BeginMultipleChoiceAction();
        this.currentBeginMultipleChoiceActionIndex = this.actionList.size();
        
        this.actionList.add(this.currentBeginMultipleChoiceAction);
        this.exitMultipleChoiceActions.clear();
    }

    public void BeginMultipleChoiceOption(String optionText) throws CaseParseException
    {
        if (this.currentBeginMultipleChoiceAction == null)
        {
            throw new CaseParseException("Cannot have BeginMultipleChoiceOption without BeginMultipleChoice.");
        }
        
        this.currentBeginMultipleChoiceAction.AddOption(optionText, this.actionList.size());
        this.actionList.add(new BeginMultipleChoiceOptionAction());
    }

    public void ExitMultipleChoice() throws CaseParseException
    {
        if (this.currentBeginMultipleChoiceAction == null)
        {
            throw new CaseParseException("Cannot have ExitMultipleChoice without BeginMultipleChoice.");
        }
        
        ExitMultipleChoiceAction exitMultipleChoiceAction = new ExitMultipleChoiceAction();
        this.exitMultipleChoiceActions.add(exitMultipleChoiceAction);
        this.actionList.add(exitMultipleChoiceAction);
    }

    public void EndMultipleChoiceOption() throws CaseParseException
    {
        if (this.currentBeginMultipleChoiceAction == null)
        {
            throw new CaseParseException("Cannot have EndMultipleChoiceOption without BeginMultipleChoice.");
        }
        
        this.actionList.add(new EndMultipleChoiceOptionAction(this.currentBeginMultipleChoiceActionIndex));
    }

    public void EndMultipleChoice() throws CaseParseException
    {
        if (this.currentBeginMultipleChoiceAction == null)
        {
            throw new CaseParseException("Cannot have EndMultipleChoice without BeginMultipleChoice.");
        }
        
        for (ExitMultipleChoiceAction exitMultipleChoiceAction : this.exitMultipleChoiceActions)
        {
            exitMultipleChoiceAction.setExitIndex(this.actionList.size());
        }
        
        this.actionList.add(new EndMultipleChoiceAction());
        this.currentBeginMultipleChoiceAction = null;
        this.currentBeginMultipleChoiceActionIndex = -1;
    }

    public void EnableFastForward() throws CaseParseException
    {
        this.actionList.add(new EnableFastForwardAction());
    }

    public void DisableFastForward() throws CaseParseException
    {
        this.actionList.add(new DisableFastForwardAction());
    }

    public void BeginBreakdown(CharacterPosition characterPosition) throws CaseParseException
    {
        this.actionList.add(new BeginBreakdownAction(characterPosition));
    }

    public void EndBreakdown() throws CaseParseException
    {
        this.actionList.add(new EndBreakdownAction());
    }
    
    public void InsertConversationSnippet(String snippetId) throws CaseParseException
    {
        Conversation snippet = Case.getInstance().getContent().GetConversationSnippetFromId(snippetId);
        
        if (snippet != null)
        {
            int indexOffset = this.actionList.size();
            
            for (Action action : snippet.actionList)
            {
                Action clonedAction = action.CloneWithOffset(indexOffset);
                
                if (!clonedAction.getClass().equals(action.getClass()))
                {
                    throw new CaseParseException("Cloned class is of different type - original class is " + action.getClass().getName() + "; cloned class is " + clonedAction.getClass().getName() + ".");
                }
                
                this.actionList.add(clonedAction);
            }
        }
        else
        {
            throw new CaseParseException("No conversation snippet with ID '" + snippetId + "'.");
        }
    }

    public void SaveToXml(CaseXmlWriter writer) throws IOException
    {
        writer.StartElement("Conversation");
        this.SaveToXmlCore(writer);
        writer.EndElement();
    }
    
    protected void SaveToXmlCore(CaseXmlWriter writer) throws IOException
    {
        if (this.id != null)
        {
            writer.WriteTextElement("Id", this.id);
        }
        
        if (this.name != null)
        {
            writer.WriteTextElement("Name", this.name);
        }
        
        if (this.unlockToId != null)
        {
            writer.WriteTextElement("UnlockToId", this.unlockToId);
        }
        
        if (this.requiredPartnerId != null)
        {
            writer.WriteTextElement("RequiredPartnerId", this.requiredPartnerId);
        }
        
        writer.WriteBooleanElement("IsEnabled", this.isEnabled);
        writer.WriteBooleanElement("HasBeenCompleted", this.hasBeenCompleted);
        
        writer.StartElement("ActionList");
        
        for (int i = 0; i < this.actionList.size(); i++)
        {
            writer.StartElement("Entry");
            this.actionList.get(i).SaveToXml(writer);
            writer.EndElement();
        }
        
        writer.EndElement();
    }
    
    public static Conversation LoadFromXml(CaseXmlReader reader)
    {
        if (reader.ElementExists("Conversation"))
        {
            return new Conversation(reader);
        }
        else if (reader.ElementExists("Interrogation"))
        {
            return new Interrogation(reader);
        }
        else if (reader.ElementExists("Confrontation"))
        {
            return new Confrontation(reader);
        }
        else
        {
            throw new IllegalArgumentException("Invalid conversation type.");
        }
    }

    public Conversation(CaseXmlReader reader)
    {
        this.Initialize();
        reader.StartElement("Conversation");
        this.LoadFromXmlCore(reader);
        reader.EndElement();
    }
    
    protected void Initialize()
    {
        this.actionList = new ArrayList<Action>();
        this.state = new State();
        this.isEnabled = false;
        this.setHasBeenCompleted(false);
    }
    
    protected void LoadFromXmlCore(CaseXmlReader reader)
    {
        if (reader.ElementExists("Id"))
        {
            this.id = reader.ReadTextElement("Id");
        }
        
        if (reader.ElementExists("Name"))
        {
            this.name = reader.ReadTextElement("Name");
        }
        
        if (reader.ElementExists("RequiredPartnerId"))
        {
            this.requiredPartnerId = reader.ReadTextElement("RequiredPartnerId");
        }
        
        this.isEnabled = reader.ReadBooleanElement("IsEnabled");
        
        reader.StartElement("ActionList");
        reader.StartList("Entry");
        
        while (reader.MoveToNextListItem())
        {
            this.actionList.add(this.GetActionForNextElement(reader));
        }
        
        reader.EndElement();
    }
    
    protected Action GetActionForNextElement(CaseXmlReader reader)
    {
        if (reader.ElementExists("MultipleCharacterChangeAction"))
        {
            return new MultipleCharacterChangeAction(reader);
        }
        else if (reader.ElementExists("CharacterChangeAction"))
        {
            return new CharacterChangeAction(reader);
        }
        else if (reader.ElementExists("SetFlagAction"))
        {
            return new SetFlagAction(reader);
        }
        else if (reader.ElementExists("BranchOnConditionAction"))
        {
            return new BranchOnConditionAction(reader);
        }
        else if (reader.ElementExists("BranchIfTrueAction"))
        {
            return new BranchIfTrueAction(reader);
        }
        else if (reader.ElementExists("BranchIfFalseAction"))
        {
            return new BranchIfFalseAction(reader);
        }
        else if (reader.ElementExists("EndBranchOnConditionAction"))
        {
            return new EndBranchOnConditionAction(reader);
        }
        else if (reader.ElementExists("ShowDialogAction"))
        {
            return new ShowDialogAction(reader);
        }
        else if (reader.ElementExists("ShowDialogAutomaticAction"))
        {
            return new ShowDialogAutomaticAction(reader);
        }
        else if (reader.ElementExists("MustPresentEvidenceAction"))
        {
            return new MustPresentEvidenceAction(reader);
        }
        else if (reader.ElementExists("SkipWrongEvidencePresentedAction"))
        {
            return new SkipWrongEvidencePresentedAction(reader);
        }
        else if (reader.ElementExists("BeginWrongEvidencePresentedAction"))
        {
            return new BeginWrongEvidencePresentedAction(reader);
        }
        else if (reader.ElementExists("EndWrongEvidencePresentedAction"))
        {
            return new EndWrongEvidencePresentedAction(reader);
        }
        else if (reader.ElementExists("SkipEndRequestedAction"))
        {
            return new SkipEndRequestedAction(reader);
        }
        else if (reader.ElementExists("BeginEndRequestedAction"))
        {
            return new BeginEndRequestedAction(reader);
        }
        else if (reader.ElementExists("EndEndRequestedAction"))
        {
            return new EndEndRequestedAction(reader);
        }
        else if (reader.ElementExists("EndMustPresentEvidenceAction"))
        {
            return new EndMustPresentEvidenceAction(reader);
        }
        else if (reader.ElementExists("EnableConversationAction"))
        {
            return new EnableConversationAction(reader);
        }
        else if (reader.ElementExists("EnableEvidenceAction"))
        {
            return new EnableEvidenceAction(reader);
        }
        else if (reader.ElementExists("UpdateEvidenceAction"))
        {
            return new UpdateEvidenceAction(reader);
        }
        else if (reader.ElementExists("NotificationAction"))
        {
            return new NotificationAction(reader);
        }
        else if (reader.ElementExists("PlayBgmAction"))
        {
            return new PlayBgmAction(reader);
        }
        else if (reader.ElementExists("PauseBgmAction"))
        {
            return new PauseBgmAction(reader);
        }
        else if (reader.ElementExists("ResumeBgmAction"))
        {
            return new ResumeBgmAction(reader);
        }
        else if (reader.ElementExists("StopBgmAction"))
        {
            return new StopBgmAction(reader);
        }
        else if (reader.ElementExists("PlayAmbianceAction"))
        {
            return new PlayAmbianceAction(reader);
        }
        else if (reader.ElementExists("PauseAmbianceAction"))
        {
            return new PauseAmbianceAction(reader);
        }
        else if (reader.ElementExists("ResumeAmbianceAction"))
        {
            return new ResumeAmbianceAction(reader);
        }
        else if (reader.ElementExists("StopAmbianceAction"))
        {
            return new StopAmbianceAction(reader);
        }
        else if (reader.ElementExists("StartAnimationAction"))
        {
            return new StartAnimationAction(reader);
        }
        else if (reader.ElementExists("StopAnimationAction"))
        {
            return new StopAnimationAction(reader);
        }
        else if (reader.ElementExists("BeginCheckPartnerAction"))
        {
            return new BeginCheckPartnerAction(reader);
        }
        else if (reader.ElementExists("BranchIfRightAction"))
        {
            return new BranchIfRightAction(reader);
        }
        else if (reader.ElementExists("BranchIfWrongAction"))
        {
            return new BranchIfWrongAction(reader);
        }
        else if (reader.ElementExists("EndCheckPartnerAction"))
        {
            return new EndCheckPartnerAction(reader);
        }
        else if (reader.ElementExists("SetPartnerAction"))
        {
            return new SetPartnerAction(reader);
        }
        else if (reader.ElementExists("GoToPresentWrongEvidenceAction"))
        {
            return new GoToPresentWrongEvidenceAction(reader);
        }
        else if (reader.ElementExists("LockConversationAction"))
        {
            return new LockConversationAction(reader);
        }
        else if (reader.ElementExists("EndCaseAction"))
        {
            return new EndCaseAction(reader);
        }
        else
        {
            throw new IllegalArgumentException("Unknown action type.");
        }
    }

    public void markChildSprites(String markParent) throws CaseParseException
    {
        ActionState state = new ActionState();
        
        for (Action a : this.actionList)
        {
            a.markChildSprites(markParent, state);
        }
    }
    
    protected class ActionState
    {
        private String leftCharacterId;
        private String rightCharacterId;
        
        public ActionState()
        {
            this.leftCharacterId = null;
            this.rightCharacterId = null;
        }
        
        public String getLeftCharacterId() { return this.leftCharacterId; }
        public void setLeftCharacterId(String leftCharacterId) { this.leftCharacterId = leftCharacterId; }
        
        public String getRightCharacterId() { return this.rightCharacterId; }
        public void setRightCharacterId(String rightCharacterId) { this.rightCharacterId = rightCharacterId; }
    }
    
    protected abstract class Action
    {
        public Action()
        {
        }

        public abstract boolean getIsSingleAction();
        public abstract void SaveToXml(CaseXmlWriter writer) throws IOException;
        protected abstract Action CloneWithOffset(int indexOffset);
        
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
        }
    }

    protected abstract class SingleAction extends Action
    {
        public SingleAction()
        {
        }

        @Override
        public boolean getIsSingleAction()
        {
            return true;
        }

        public abstract void Execute(State state);
    }

    protected abstract class ContinuousAction extends Action
    {
        protected State state;

        public ContinuousAction()
        {
        }

        @Override
        public boolean getIsSingleAction()
        {
            return false;
        }

        protected boolean isFinished;

        public boolean getIsFinished() { return this.isFinished; }
        public void setIsFinished(boolean isFinished) { this.isFinished = isFinished; }

        public boolean getIsReadyToHide()
        {
            return true;
        }

        public boolean getShouldSkip()
        {
            return false;
        }

        public void GoToNext(State state)
        {
            state.setActionIndex(state.getActionIndex() + 1);
        }

        public void Begin(State state) throws CaseParseException
        {
            this.state = state;
            this.Reset();
        }

        public abstract void Update(GameContainer gc, int delta);
        public abstract void Draw(GameContainer gc, Graphics g, float yOffset) throws SlickException;

        public void Reset()
        {
            this.setIsFinished(false);
        }
    }

    protected class MultipleCharacterChangeAction extends ContinuousAction
    {
        private ArrayList<CharacterChangeAction> characterChangeList;

        public MultipleCharacterChangeAction()
        {
            this.characterChangeList = new ArrayList<CharacterChangeAction>();
        }

        public void AddCharacterChange(CharacterChangeAction characterChange)
        {
            this.characterChangeList.add(characterChange);
        }

        @Override
        public void Begin(State state) throws CaseParseException
        {
            super.Begin(state);

            for (int i = 0; i < this.characterChangeList.size(); i++)
            {
                this.characterChangeList.get(i).Begin(state);
            }
        }

        @Override
        public void Update(GameContainer gc, int delta)
        {
            boolean allCharacterChangesFinished = true;

            for (int i = 0; i < this.characterChangeList.size(); i++)
            {
                CharacterChangeAction characterChange = this.characterChangeList.get(i);

                characterChange.Update(gc, delta);

                if (!characterChange.getIsFinished())
                {
                    allCharacterChangesFinished = false;
                }
            }

            if (allCharacterChangesFinished)
            {
                this.setIsFinished(true);
            }
        }

        @Override
        public void Draw(GameContainer gc, Graphics g, float yOffset)
        {
            for (int i = 0; i < this.characterChangeList.size(); i++)
            {
                this.characterChangeList.get(i).Draw(gc, g, yOffset);
            }
        }

        @Override
        public void Reset()
        {
            super.Reset();

            if (this.characterChangeList != null)
            {
                for (int i = 0; i < this.characterChangeList.size(); i++)
                {
                    this.characterChangeList.get(i).Reset();
                }
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("MultipleCharacterChangeAction");
            
            writer.StartElement("CharacterChangeList");
            
            for (int i = 0; i < this.characterChangeList.size(); i++)
            {
                writer.StartElement("Entry");
                this.characterChangeList.get(i).SaveToXml(writer);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.EndElement();
        }

        private MultipleCharacterChangeAction(CaseXmlReader reader)
        {
            this.characterChangeList = new ArrayList<CharacterChangeAction>();
            reader.StartElement("MultipleCharacterChangeAction");
            
            reader.StartElement("CharacterChangeList");
            reader.StartList("Entry");
            
            while (reader.MoveToNextListItem())
            {
                this.characterChangeList.add(new CharacterChangeAction(reader));
            }
            
            reader.EndElement();
            
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            MultipleCharacterChangeAction action = new MultipleCharacterChangeAction();
            
            for (CharacterChangeAction characterChangeAction : characterChangeList)
            {
                action.AddCharacterChange((CharacterChangeAction)characterChangeAction.CloneWithOffset(indexOffset));
            }
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
            for (Action a : this.characterChangeList)
            {
                a.markChildSprites(markParent, state);
            }
        }
    }

    protected class CharacterChangeAction extends ContinuousAction
    {
        private CharacterPosition position;
        private String characterIdToChangeTo;
        private String initialEmotionId;
        private String sfxId;

        private EasingFunction characterOutEase;
        private EasingFunction characterInEase;

        public CharacterChangeAction(CharacterPosition position, String characterIdToChangeTo, String initialEmotionId, String sfxId)
        {
            this.position = position;
            this.characterIdToChangeTo = characterIdToChangeTo;
            this.initialEmotionId = initialEmotionId;
            this.sfxId = sfxId; 

            this.characterOutEase = new QuadraticEase(0, position == CharacterPosition.Left ? -Settings.DisplayBounds.getX() / 2 : Settings.DisplayBounds.getX() / 2, 250);
            this.characterInEase = new QuadraticEase(position == CharacterPosition.Left ? -Settings.DisplayBounds.getX() / 2 : Settings.DisplayBounds.getX() / 2, 0, 250);
        }

        @Override
        public void Begin(State state) throws CaseParseException
        {
            super.Begin(state);

            this.characterOutEase.Begin();
            this.characterInEase.Begin();

            // First check to see if the character in question to move in
            // is already there - nothing to do in that case.
            if (((position == CharacterPosition.Left && Utils.equals(this.state.getLeftCharacterId(), this.characterIdToChangeTo)) ||
                (position == CharacterPosition.Right && Utils.equals(this.state.getRightCharacterId(), this.characterIdToChangeTo))))
            {
                if (position == CharacterPosition.Left)
                {
                    state.setLeftCharacterId(this.characterIdToChangeTo);
                    state.setLeftCharacterEmotionId(this.initialEmotionId);
                }
                else if (position == CharacterPosition.Right)
                {
                    state.setRightCharacterId(this.characterIdToChangeTo);
                    state.setRightCharacterEmotionId(this.initialEmotionId);
                }

                this.setIsFinished(true);
            }
            else
            {
                if (position == CharacterPosition.Left)
                {
                    this.state.setLeftCharacterXOffsetEasingFunction(this.characterOutEase);
                    this.state.setLeftReplacementCharacterId(this.characterIdToChangeTo);
                    this.state.setLeftReplacementCharacterEmotionId(this.initialEmotionId);
                    this.state.setLeftReplacementCharacterXOffsetEasingFunction(this.characterInEase);
                }
                else if (position == CharacterPosition.Right)
                {
                    this.state.setRightCharacterXOffsetEasingFunction(this.characterOutEase);
                    this.state.setRightReplacementCharacterId(this.characterIdToChangeTo);
                    this.state.setRightReplacementCharacterEmotionId(this.initialEmotionId);
                    this.state.setRightReplacementCharacterXOffsetEasingFunction(this.characterInEase);
                }
            }
        }

        @Override
        public void Update(GameContainer gc, int delta)
        {
            if (this.getIsFinished())
            {
                return;
            }

            if (position == CharacterPosition.Left && Utils.isNullOrEmpty(this.state.getLeftCharacterId()))
            {
                this.characterOutEase.Finish();
            }
            else if (position == CharacterPosition.Right && Utils.isNullOrEmpty(this.state.getRightCharacterId()))
            {
                this.characterOutEase.Finish();
            }

            if (this.characterOutEase.getIsFinished() && this.characterInEase.getIsFinished())
            {
                this.setIsFinished(true);
                this.SwapCharacters();
                return;
            }

            this.characterOutEase.Update(gc,delta);
            this.characterInEase.Update(gc,delta);
        }

        @Override
        public void Draw(GameContainer gc, Graphics g, float yOffset)
        {
            // The characters are drawn by the encounter,
            // so there's nothing for us to do here.
        }

        private void SwapCharacters()
        {
            if (position == CharacterPosition.Left)
            {
                state.setLeftCharacterId(this.characterIdToChangeTo);
                state.setLeftCharacterEmotionId(this.initialEmotionId);
                state.setLeftCharacterXOffsetEasingFunction(null);
                state.setLeftReplacementCharacterId(null);
                state.setLeftReplacementCharacterXOffsetEasingFunction(null);

                if (Utils.isNullOrEmpty(this.characterIdToChangeTo))
                {
                    this.characterInEase.Finish();
                    this.setIsFinished(true);
                }
            }
            else if (position == CharacterPosition.Right)
            {
                state.setRightCharacterId(this.characterIdToChangeTo);
                state.setRightCharacterEmotionId(this.initialEmotionId);
                state.setRightCharacterXOffsetEasingFunction(null);
                state.setRightReplacementCharacterId(null);
                state.setRightReplacementCharacterXOffsetEasingFunction(null);

                if (!Utils.isNullOrEmpty(this.characterIdToChangeTo))
                {
                    this.characterInEase.Finish();
                    this.setIsFinished(true);
                }
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("CharacterChangeAction");
            writer.WriteTextElement("Position", this.position.name());
            writer.WriteTextElement("CharacterIdToChangeTo", this.characterIdToChangeTo);
            writer.WriteTextElement("InitialEmotionId", this.initialEmotionId);
            
            if (this.sfxId != null)
            {
                writer.WriteTextElement("SfxId", this.sfxId);
            }
            
            writer.EndElement();
        }

        private CharacterChangeAction(CaseXmlReader reader)
        {
            reader.StartElement("CharacterChangeAction");
            this.position = CharacterPosition.valueOf(reader.ReadTextElement("Position"));
            this.characterIdToChangeTo = reader.ReadTextElement("CharacterIdToChangeTo");
            
            if (this.characterIdToChangeTo.equals("None"))
            {
                this.characterIdToChangeTo = null;
            }
            
            this.initialEmotionId = reader.ReadTextElement("InitialEmotionId");
            reader.EndElement();
            
            this.characterOutEase = new QuadraticEase(0, position == CharacterPosition.Left ? -Settings.DisplayBounds.getX() / 2 : Settings.DisplayBounds.getX() / 2, 250);
            this.characterInEase = new QuadraticEase(position == CharacterPosition.Left ? -Settings.DisplayBounds.getX() / 2 : Settings.DisplayBounds.getX() / 2, 0, 250);
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            CharacterChangeAction action = new CharacterChangeAction(this.position, this.characterIdToChangeTo, this.initialEmotionId, this.sfxId);
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
            if (this.position == CharacterPosition.Left)
            {
                state.setLeftCharacterId(this.characterIdToChangeTo);
            }
            else if (this.position == CharacterPosition.Right)
            {
                state.setRightCharacterId(this.characterIdToChangeTo);
            }
            
            if ((this.position == CharacterPosition.Left || this.position == CharacterPosition.Right) && this.characterIdToChangeTo != null && !this.characterIdToChangeTo.equals("None"))
            {
                DialogCharacter d = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(this.characterIdToChangeTo);
                
                if (d == null)
                {
                    throw new CaseParseException("No character with ID '" + this.characterIdToChangeTo + "'.");
                }
                
                d.markChildSprites(markParent, this.initialEmotionId);
            }
        }
    }

    protected class SetFlagAction extends SingleAction
    {
        private String flagId;

        public SetFlagAction(String flagId)
        {
            this.flagId = flagId;
        }

        @Override
        public void Execute(State state)
        {
            Case.getInstance().getFlagManager().SetFlag(this.flagId);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SetFlagAction");
            writer.WriteTextElement("FlagId", this.flagId);
            writer.EndElement();
        }

        private SetFlagAction(CaseXmlReader reader)
        {
            reader.StartElement("SetFlagAction");
            this.flagId = reader.ReadTextElement("FlagId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SetFlagAction action = new SetFlagAction(this.flagId);
            
            return action;
        }
    }

    protected class BranchOnConditionAction extends SingleAction
    {
        private Condition condition;

        public BranchOnConditionAction(Condition condition)
        {
            this.condition = condition;
        }

        // Action index to branch to if the flag is true.
        private int trueIndex;

        public int getTrueIndex() { return this.trueIndex; }
        public void setTrueIndex(int trueIndex) { this.trueIndex = trueIndex; }

        // Action index to branch to if the flag is false.
        private int falseIndex;

        public int getFalseIndex() { return this.falseIndex; }
        public void setFalseIndex(int falseIndex) { this.falseIndex = falseIndex; }

        @Override
        public void Execute(State state)
        {
            if (this.condition.IsTrue())
            {
                state.setActionIndex(this.getTrueIndex());
            }
            else
            {
                state.setActionIndex(this.getFalseIndex());
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BranchOnConditionAction");
            
            writer.StartElement("Condition");
            this.condition.SaveToXml(writer);
            writer.EndElement();
            
            writer.WriteIntElement("TrueIndex", this.trueIndex);
            writer.WriteIntElement("FalseIndex", this.falseIndex);
            writer.EndElement();
        }

        private BranchOnConditionAction(CaseXmlReader reader)
        {
            reader.StartElement("BranchOnConditionAction");
            
            reader.StartElement("Condition");
            //this.condition = new Condition(reader);
            reader.EndElement();
            
            this.trueIndex = reader.ReadIntElement("TrueIndex");
            this.falseIndex = reader.ReadIntElement("FalseIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BranchOnConditionAction action = new BranchOnConditionAction(this.condition.Clone());
            
            action.setTrueIndex(this.trueIndex + indexOffset);
            action.setFalseIndex(this.falseIndex + indexOffset);
            
            return action;
        }
    }

    // Dummy placeholder to jump to when the tested condition is true.
    protected class BranchIfTrueAction extends SingleAction
    {
        public BranchIfTrueAction()
        {
        }

        // Action index to branch to representing the end of this branch statement.
        private int endIndex;

        public int getEndIndex() { return this.endIndex; }
        public void setEndIndex(int endIndex) { this.endIndex = endIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getEndIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BranchIfTrueAction");
            writer.WriteIntElement("EndIndex", this.endIndex);
            writer.EndElement();
        }

        private BranchIfTrueAction(CaseXmlReader reader)
        {
            reader.StartElement("BranchIfTrueAction");
            this.endIndex = reader.ReadIntElement("EndIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BranchIfTrueAction action = new BranchIfTrueAction();
            
            action.setEndIndex(this.endIndex + indexOffset);
            
            return action;
        }
    }

    // Dummy placeholder to jump to when the tested condition is false.
    protected class BranchIfFalseAction extends SingleAction
    {
        public BranchIfFalseAction()
        {
        }

        // Action index to branch to representing the end of this branch statement.
        private int endIndex;

        public int getEndIndex() { return this.endIndex; }
        public void setEndIndex(int endIndex) { this.endIndex = endIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getEndIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BranchIfFalseAction");
            writer.WriteIntElement("EndIndex", this.endIndex);
            writer.EndElement();
        }

        private BranchIfFalseAction(CaseXmlReader reader) 
        {
            reader.StartElement("BranchIfFalseAction");
            this.endIndex = reader.ReadIntElement("EndIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BranchIfFalseAction action = new BranchIfFalseAction();
            
            action.setEndIndex(this.endIndex + indexOffset);
            
            return action;
        }
    }

    // Dummy placeholder to jump to when finished with a branch on condition action.
    protected class EndBranchOnConditionAction extends SingleAction
    {
        public EndBranchOnConditionAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndBranchOnConditionAction");
            writer.EndElement();
        }

        private EndBranchOnConditionAction(CaseXmlReader reader)
        {
            reader.StartElement("EndBranchOnConditionAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            return new EndBranchOnConditionAction();
        }
    }

    protected class ShowDialogAction extends ContinuousAction implements DialogEventListener
    {
        private static final double MillisecondsBetweenMouthMovement = 100;

        private String id;
        
        protected CharacterPosition speakerPosition;
        protected String characterId;
        protected String rawDialog;
        protected String filePath;
        protected int leadInTime;

        protected Dialog dialog;

        private double totalMillisecondsSinceMouthChange;
        private boolean isTalking;

        public ShowDialogAction()
        {
        }

        public ShowDialogAction(CharacterPosition speakerPosition, String characterId, String rawDialog, String filePath, int leadInTime) throws CaseParseException
        {
            this.id = DigestUtils.md5Hex(speakerPosition.toString() + "$" + characterId + "$" + rawDialog + "$" + filePath + "$" + leadInTime);

            if (speakerPosition == CharacterPosition.None && this.getIsInterrogation())
            {
                throw new CaseParseException("Cannot have interrogation dialog without a speaker.");
            }
            else if (speakerPosition == CharacterPosition.Offscreen && characterId == null)
            {
                throw new CaseParseException("Cannot have an offscreen speaker without a charater ID.");
            }

            this.speakerPosition = speakerPosition;
            this.characterId = characterId;
            this.rawDialog = rawDialog;
            this.filePath = filePath;
            this.leadInTime = leadInTime;
            this.isTalking = false;
                
            String parsedString = this.rawDialog;

            while (parsedString.indexOf('{') >= 0 && parsedString.indexOf('}') >= 0)
            {
                int eventStart = parsedString.indexOf('{');
                int eventEnd = parsedString.indexOf('}');

                String[] eventComponents = parsedString.substring(eventStart + 1, eventEnd).split(":");
                String replacementText = "";
                String testString = eventComponents[0].toLowerCase();

                if (Utils.equals(testString, "playsound"))
                {
                    String sfxId = eventComponents[1];
                    Case.getInstance().getAudioManager().RequestSfx(sfxId);
                }
                else if (Utils.equals(testString, "playbgm"))
                {
                    String bgmId = eventComponents[1];
                    Case.getInstance().getAudioManager().RequestBgm(bgmId);
                }
                else if (Utils.equals(testString, "playbgmpermanently"))
                {
                    String bgmId = eventComponents[1];
                    Case.getInstance().getAudioManager().RequestBgm(bgmId);
                }
                else if (Utils.equals(testString, "playambiance"))
                {
                    String ambianceSfxId = eventComponents[1];
                    Case.getInstance().getAudioManager().RequestSfx(ambianceSfxId);
                }

                parsedString = parsedString.substring(0, eventStart) + replacementText + parsedString.substring(eventEnd + 1);
            }
        }

        @Override
        public boolean getIsReadyToHide()
        {
            return this.dialog.getIsReadyToHide();
        }

        protected boolean getIsInterrogation()
        {
            return false;
        }

        protected boolean getIsPassive()
        {
            return false;
        }

        protected int getDelayBeforeContinuing()
        {
            return -1;
        }

        protected boolean isInConfrontation;

        public boolean getIsInConfrontation() { return this.isInConfrontation; }
        public void setIsInConfrontation(boolean isInConfrontation) { this.isInConfrontation = isInConfrontation; }

        protected boolean getCanNavigateBack()
        {
            return false;
        }

        protected boolean getCanNavigateForward()
        {
            return false;
        }

        protected boolean getShouldPresentEvidenceAutomatically()
        {
            return false;
        }

        @Override
        public void Begin(State state) throws CaseParseException
        {
            super.Begin(state);

            if (this.dialog == null)
            {
                if (this.speakerPosition == CharacterPosition.Unknown)
                {
                    this.dialog = Dialog.CreateForString("????", this.rawDialog, this.filePath, this.getDelayBeforeContinuing(), false /* isInterrogation */, false /* isPassive */, false /* isConfrontation */, false /* canNavigateBack */, false /* canNavigateForward */, false /* presentEvidenceAutomatically */);
                }
                else if (this.speakerPosition == CharacterPosition.Offscreen)
                {
                    this.dialog = Dialog.CreateForString(Case.getInstance().getDialogCharacterManager().GetCharacterNameFromId(this.characterId), this.rawDialog, this.filePath, this.getDelayBeforeContinuing(), false /* isInterrogation */, false /* isPassive */, false /* isConfrontation */, false /* canNavigateBack */, false /* canNavigateForward */, false /* presentEvidenceAutomatically */);
                }
                else if (this.speakerPosition != CharacterPosition.None)
                {
                    String characterId = this.speakerPosition == CharacterPosition.Left ? state.getLeftCharacterId() : state.getRightCharacterId();
                    this.dialog = Dialog.CreateForString(Case.getInstance().getDialogCharacterManager().GetCharacterNameFromId(characterId), this.rawDialog, this.filePath, this.getDelayBeforeContinuing(), this.getIsInterrogation(), this.getIsPassive(), this.getIsInConfrontation(), this.getCanNavigateBack(), this.getCanNavigateForward(), this.getShouldPresentEvidenceAutomatically());
                }
                else
                {
                    this.dialog = Dialog.CreateForString("", this.rawDialog, this.filePath, this.getDelayBeforeContinuing(), false /* isInterrogation */, false /* isPassive */, false /* isConfrontation */, false /* canNavigateBack */, false /* canNavigateForward */, false /* presentEvidenceAutomatically */);
                }
            }
            
            DialogEventProvider.RegisterListener(this);
            this.dialog.Begin();
        }

        @Override
        public void Update(GameContainer gc, int delta)
        {
            if ((MouseHelper.ClickedAnywhere() && !this.dialog.HandleClick() && !this.dialog.getIsAutomatic() && !this.getShouldPresentEvidenceAutomatically()) || (this.dialog.getIsFinished() && (this.dialog.getIsAutomatic() || this.getShouldPresentEvidenceAutomatically())))
            {
                if (this.dialog.getIsFinished())
                {
                    this.setIsFinished(true);
                    this.state.setLeftCharacterMouthOffCount(0);
                    this.state.setRightCharacterMouthOffCount(0);
                }
            }
            else if (this.dialog.getIsFinished() && this.state.getCurrentAnimation() != null)
            {
                this.state.getCurrentAnimation().FinishCurrentFrame();
            }
            
            boolean shouldChangeMouth = false;

            if (this.isTalking)
            {
                this.totalMillisecondsSinceMouthChange += delta;

                if (this.totalMillisecondsSinceMouthChange > MillisecondsBetweenMouthMovement)
                {
                    shouldChangeMouth = true;
                    this.totalMillisecondsSinceMouthChange -= MillisecondsBetweenMouthMovement;
                }
            }
            else
            {
                this.totalMillisecondsSinceMouthChange = 0;
            }

            this.dialog.Update(gc,delta);
            boolean oldIsTalking = this.isTalking;
            this.isTalking =
                !this.dialog.getIsAudioPaused() &&
                !this.dialog.getIsFinished() &&
                (this.speakerPosition == CharacterPosition.Left ? this.state.getLeftCharacterMouthOffCount() <= 0 : this.state.getRightCharacterMouthOffCount() <= 0);

            if (!this.isTalking)
            {
                this.totalMillisecondsSinceMouthChange = 0;
            }
            else if (oldIsTalking == false)
            {
                shouldChangeMouth = true;
            }

            if (this.speakerPosition == CharacterPosition.Left)
            {
                this.state.setIsLeftCharacterTalking(this.isTalking);
                this.state.setShouldLeftCharacterChangeMouth(shouldChangeMouth);
            }
            else if (this.speakerPosition == CharacterPosition.Right)
            {
                this.state.setIsRightCharacterTalking(this.isTalking);
                this.state.setShouldRightCharacterChangeMouth(shouldChangeMouth);
            }
        }

        @Override
        public void Draw(GameContainer gc, Graphics g, float yOffset) throws SlickException
        {
            this.dialog.Draw(gc, g, yOffset);
        }

        @Override
        public void Reset()
        {
            super.Reset();

            if (this.dialog != null)
            {
                this.dialog.Reset();
            }

            this.totalMillisecondsSinceMouthChange = 0;
            this.isTalking = false;

            this.state.setLeftCharacterXOffsetEasingFunction(null);
            this.state.setRightCharacterXOffsetEasingFunction(null);

            DialogEventProvider.ClearListener(this);
        }

        public void OnDialogSpeakerEmotionChanged(Dialog sender, String newEmotionId)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.speakerPosition == CharacterPosition.Left)
            {
                this.state.setLeftCharacterEmotionId(newEmotionId);
            }
            else if (this.speakerPosition == CharacterPosition.Right)
            {
                this.state.setRightCharacterEmotionId(newEmotionId);
            }
        }

        public void OnDialogOtherEmotionChanged(Dialog sender, String newEmotionId)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.speakerPosition == CharacterPosition.Left)
            {
                this.state.setRightCharacterEmotionId(newEmotionId);
            }
            else if (this.speakerPosition == CharacterPosition.Right)
            {
                this.state.setLeftCharacterEmotionId(newEmotionId);
            }
        }

        public void OnDialogNextFrame(Dialog sender)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.state.getCurrentAnimation() != null)
            {
                this.state.getCurrentAnimation().MoveToNextFrame();
            }
        }

        public void OnDialogPlayerDamaged(Dialog sender)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.state.getCurrentConfrontation() != null)
            {
                this.state.getCurrentConfrontation().DamagePlayer();
            }
        }

        public void OnDialogOpponentDamaged(Dialog sender)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.state.getCurrentConfrontation() != null)
            {
                this.state.getCurrentConfrontation().DamageOpponent();
            }
        }

        public void OnDialogSpeakerStartedShaking(Dialog sender)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.speakerPosition == CharacterPosition.Left)
            {
                this.state.setLeftCharacterXOffsetEasingFunction(new EarthquakeEase(0, -50, 250));
                this.state.getLeftCharacterXOffsetEasingFunction().Begin();
            }
            else if (this.speakerPosition == CharacterPosition.Right)
            {
                this.state.setRightCharacterXOffsetEasingFunction(new EarthquakeEase(0, -50, 250));
                this.state.getRightCharacterXOffsetEasingFunction().Begin();
            }
        }

        public void OnDialogSpeakerMouthStateChanged(Dialog sender, boolean isMouthOn)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.speakerPosition == CharacterPosition.Left)
            {
                // We'll keep a count instead of just a boolean variable, so that way
                // something of the form MouthOff, MouthOff, MouthOn doesn't result
                // in the mouth being on at that point.
                this.state.setLeftCharacterMouthOffCount(this.state.getLeftCharacterMouthOffCount() + (isMouthOn ? -1 : 1));
            }
            else if (this.speakerPosition == CharacterPosition.Right)
            {
                this.state.setRightCharacterMouthOffCount(this.state.getRightCharacterMouthOffCount() + (isMouthOn ? -1 : 1));
            }
        }
        
        public void OnDialogDirectlyNavigated(Dialog sender, DirectNavigationDirection direction)
        {
        }
        
        public void OnDialogPressForInfoClicked(Dialog sender)
        {
        }
        
        public void OnDialogUsePartner(Dialog sender)
        {
        }
        
        public void OnDialogEvidencePresented(Dialog sender, String evidenceId)
        {
        }
        
        public void OnDialogEndRequested(Dialog sender)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ShowDialogAction");
            this.SaveToXmlCore(writer);
            writer.EndElement();
        }
        
        protected void SaveToXmlCore(CaseXmlWriter writer) throws IOException
        {
            writer.WriteTextElement("Id", this.id);
            writer.WriteTextElement("SpeakerPosition", this.speakerPosition.name());
            writer.WriteTextElement("RawDialog", this.rawDialog);
            
            if (this.filePath != null)
            {
                writer.WriteTextElement("FilePath", this.filePath);
            }
            
            writer.WriteIntElement("LeadInTime", this.leadInTime);
            
            if (this.characterId != null)
            {
                writer.WriteTextElement("CharacterId", this.characterId);
            }
            
            writer.WriteBooleanElement("IsInConfrontation", this.isInConfrontation);
        }

        private ShowDialogAction(CaseXmlReader reader)
        {
            reader.StartElement("ShowDialogAction");
            this.LoadFromXmlCore(reader);
            reader.EndElement();
        }
        
        protected void LoadFromXmlCore(CaseXmlReader reader)
        {
            this.id = reader.ReadTextElement("Id");
            this.speakerPosition = CharacterPosition.valueOf(reader.ReadTextElement("SpeakerPosition"));
            this.rawDialog = reader.ReadTextElement("RawDialog");
            
            if (reader.ElementExists("FilePath"))
            {
                this.filePath = reader.ReadTextElement("FilePath");
            }
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            ShowDialogAction action = null;
            
            try
            {
                action = new ShowDialogAction(this.speakerPosition, this.characterId, this.rawDialog, this.filePath, this.leadInTime);
                action.setIsInConfrontation(this.isInConfrontation);
            }
            catch (CaseParseException ex)
            {
                Logger.getLogger(Conversation.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
            String characterId = null;
            String otherCharacterId = null;
            
            if (this.speakerPosition == CharacterPosition.Left)
            {
                characterId = state.getLeftCharacterId();
                otherCharacterId = state.getRightCharacterId();
            }
            else if (this.speakerPosition == CharacterPosition.Right)
            {
                characterId = state.getRightCharacterId();
                otherCharacterId = state.getLeftCharacterId();
            }
            
            if (characterId != null || otherCharacterId != null)
            {
                if (characterId == null)
                {
                    throw new CaseParseException("Speaker is null. Speaker position:  " + this.speakerPosition.name() + ", Raw Dialog: " + this.rawDialog);
                }
                
                ArrayList<String> characterEmotionList = new ArrayList<String>();
                ArrayList<String> otherCharacterEmotionList = new ArrayList<String>();
                boolean breakdownPresent = false;
                
                String parsedString = this.rawDialog;
                
                while (parsedString.indexOf('{') >= 0 && parsedString.indexOf('}') >= 0)
                {
                    int eventStart = parsedString.indexOf('{');
                    int eventEnd = parsedString.indexOf('}');

                    String[] eventComponents = parsedString.substring(eventStart + 1, eventEnd).split(":");
                    String replacementText = "";
                    String testString = eventComponents[0].toLowerCase();

                    if (Utils.equals(testString, "emotion"))
                    {
                        String newEmotion = eventComponents[1];
                        characterEmotionList.add(newEmotion);
                    }
                    else if (Utils.equals(testString, "otheremotion"))
                    {
                        String newOtherEmotion = eventComponents[1];
                        otherCharacterEmotionList.add(newOtherEmotion);
                    }
                    else if (Utils.equals(testString, "zoom"))
                    {
                        characterEmotionList.add("Zoom");
                    }
                    else if (Utils.equals(testString, "beginbreakdown"))
                    {
                        breakdownPresent = true;
                    }

                    parsedString = parsedString.substring(0, eventStart) + replacementText + parsedString.substring(eventEnd + 1);
                }
                
                if (characterId != null)
                {
                    DialogCharacter d = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(characterId);

                    for (String characterEmotion : characterEmotionList)
                    {
                        try
                        {
                            d.markChildSprites(markParent, characterEmotion);
                        }
                        catch (CaseParseException ex)
                        {
                            throw new CaseParseException(ex.getMessage() + " in dialog '" + this.rawDialog + "'.");
                        }
                    }
                }
                
                if (otherCharacterId != null)
                {
                    DialogCharacter d = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(otherCharacterId);

                    for (String otherCharacterEmotion : otherCharacterEmotionList)
                    {
                        try
                        {
                            d.markChildSprites(markParent, otherCharacterEmotion);
                        }
                        catch (CaseParseException ex)
                        {
                            throw new CaseParseException(ex.getMessage() + " in dialog '" + this.rawDialog + "'.");
                        }
                    }
                }
                
                if (breakdownPresent)
                {
                    DialogCharacter d = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(characterId);
                    d.markBreakdownChildSprites(markParent);
                }
            }
        }
    }

    protected class ShowDialogAutomaticAction extends ShowDialogAction implements DialogEventListener
    {
        private int delayBeforeContinuing;
        
        public ShowDialogAutomaticAction()
        {
        }

        public ShowDialogAutomaticAction(CharacterPosition speakerPosition, String characterId, String rawDialog, String filePath, int leadInTime, int delayBeforeContinuing) throws CaseParseException
        {
            super(speakerPosition, characterId, rawDialog, filePath, leadInTime);
            this.delayBeforeContinuing = delayBeforeContinuing;
        }

        @Override
        protected int getDelayBeforeContinuing()
        {
            return this.delayBeforeContinuing;
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ShowDialogAutomaticAction");
            this.SaveToXmlCore(writer);
            writer.WriteIntElement("DelayBeforeContinuing", this.delayBeforeContinuing);
            writer.EndElement();
        }

        private ShowDialogAutomaticAction(CaseXmlReader reader)
        {
            reader.StartElement("ShowDialogAutomaticAction");
            this.LoadFromXmlCore(reader);
            this.delayBeforeContinuing = reader.ReadIntElement("DelayBeforeContinuing");
            reader.EndElement();
        }
    }

    protected class MustPresentEvidenceAction extends ShowDialogAction
    {
        private ArrayList<String> correctEvidenceIdList;

        public MustPresentEvidenceAction(CharacterPosition speakerPosition, String characterId, String rawDialog, String filePath, int leadInTime) throws CaseParseException 
        {
            super(speakerPosition, characterId, rawDialog, filePath, leadInTime);
            
            this.correctEvidenceIdList = new ArrayList<String>();

            this.correctEvidencePresentedIndex = -1;
            this.wrongEvidencePresentedIndex = -1;
            this.endRequestedIndex = -1;
        }

        @Override
        protected boolean getShouldPresentEvidenceAutomatically()
        {
            return true;
        }

        private int correctEvidencePresentedIndex;

        public int getCorrectEvidencePresentedIndex() { return this.correctEvidencePresentedIndex; }
        public void setCorrectEvidencePresentedIndex(int correctEvidencePresentedIndex) { this.correctEvidencePresentedIndex = correctEvidencePresentedIndex; }

        private int wrongEvidencePresentedIndex;

        public int getWrongEvidencePresentedIndex() { return this.wrongEvidencePresentedIndex; }
        public void setWrongEvidencePresentedIndex(int wrongEvidencePresentedIndex) { this.wrongEvidencePresentedIndex = wrongEvidencePresentedIndex; }

        private int endRequestedIndex;

        public int getEndRequestedIndex() { return this.endRequestedIndex; }
        public void setEndRequestedIndex(int endRequestedIndex) { this.endRequestedIndex = endRequestedIndex; }

        @Override
        public void Begin(State state) throws CaseParseException
        {
            super.Begin(state);
            
            // If we don't have evidence yet, then we have no evidence to present.
            // We'll just early-out and go to the end-requested index.
            if (!Case.getInstance().getEvidenceManager().getHasEvidence())
            {
                this.state.setActionIndex(this.getEndRequestedIndex());
                this.setIsFinished(true);
            }
        }

        @Override
        public void OnDialogEvidencePresented(Dialog sender, String evidenceId)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            if (this.correctEvidenceIdList.contains(evidenceId))
            {
                this.state.setActionIndex(this.getCorrectEvidencePresentedIndex());
            }
            else
            {
                this.state.setActionIndex(this.getWrongEvidencePresentedIndex());
            }

            this.setIsFinished(true);
        }

        @Override
        public void OnDialogEndRequested(Dialog sender)
        {
            if (sender != this.dialog)
            {
                return;
            }
            
            this.state.setActionIndex(this.getEndRequestedIndex());
            this.setIsFinished(true);
        }

        @Override
        public void Reset()
        {
            super.Reset();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("MustPresentEvidenceAction");
            this.SaveToXmlCore(writer);
            writer.EndElement();
        }
        
        @Override
        protected void SaveToXmlCore(CaseXmlWriter writer) throws IOException
        {
            super.SaveToXmlCore(writer);
            
            writer.StartElement("CorrectEvidenceIdList");
            
            for (String correctEvidenceId : this.correctEvidenceIdList)
            {
                writer.StartElement("Entry");
                writer.WriteTextElement("CorrectEvidenceId", correctEvidenceId);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.WriteIntElement("CorrectEvidencePresentedIndex", this.correctEvidencePresentedIndex);
            writer.WriteIntElement("WrongEvidencePresentedIndex", this.wrongEvidencePresentedIndex);
            writer.WriteIntElement("EndRequestedIndex", this.endRequestedIndex);
        }

        private MustPresentEvidenceAction(CaseXmlReader reader)
        {
            reader.StartElement("MustPresentEvidenceAction");
            this.LoadFromXmlCore(reader);
            reader.EndElement();
        }
        
        @Override
        protected void LoadFromXmlCore(CaseXmlReader reader)
        {
            super.LoadFromXmlCore(reader);
            
            this.correctEvidenceIdList = new ArrayList<String>();
            
            reader.StartElement("CorrectEvidenceIdList");
            reader.StartList("Entry");
            
            while (reader.MoveToNextListItem())
            {
                this.correctEvidenceIdList.add(reader.ReadTextElement("CorrectEvidenceId"));
            }
            
            reader.EndElement();
            
            this.correctEvidencePresentedIndex = reader.ReadIntElement("CorrectEvidencePresentedIndex");
            this.wrongEvidencePresentedIndex = reader.ReadIntElement("WrongEvidencePresentedIndex");
            this.endRequestedIndex = reader.ReadIntElement("EndRequestedIndex");
        }
        
        @Override
        protected Action CloneWithOffset(int indexOffset)
        {
            MustPresentEvidenceAction action = null;
            
            try
            {
                action = new MustPresentEvidenceAction(this.speakerPosition, this.characterId, this.rawDialog, this.filePath, this.leadInTime);
                
                for (String correctEvidenceId : this.correctEvidenceIdList)
                {
                    action.AddCorrectEvidenceId(correctEvidenceId);
                }
            
                action.setIsInConfrontation(this.isInConfrontation);
                action.setCorrectEvidencePresentedIndex(this.correctEvidencePresentedIndex + indexOffset);
                action.setWrongEvidencePresentedIndex(this.wrongEvidencePresentedIndex + indexOffset);
                action.setEndRequestedIndex(this.endRequestedIndex + indexOffset);
            }
            catch (CaseParseException ex)
            {
                Logger.getLogger(Conversation.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            return action;
        }

        private void AddCorrectEvidenceId(String correctEvidenceId)
        {
            this.correctEvidenceIdList.add(correctEvidenceId);
        }
    }

    protected class SkipWrongEvidencePresentedAction extends SingleAction
    {
        public SkipWrongEvidencePresentedAction()
        {
            this.afterWrongEvidencePresentedIndex = -1;
        }

        private int afterWrongEvidencePresentedIndex;

        public int getAfterWrongEvidencePresentedIndex() { return this.afterWrongEvidencePresentedIndex; }
        public void setAfterWrongEvidencePresentedIndex(int afterWrongEvidencePresentedIndex) { this.afterWrongEvidencePresentedIndex = afterWrongEvidencePresentedIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getAfterWrongEvidencePresentedIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SkipWrongEvidencePresentedAction");
            writer.WriteIntElement("AfterWrongEvidencePresentedIndex", this.afterWrongEvidencePresentedIndex);
            writer.EndElement();
        }

        private SkipWrongEvidencePresentedAction(CaseXmlReader reader)
        {
            reader.StartElement("SkipWrongEvidencePresentedAction");
            this.afterWrongEvidencePresentedIndex = reader.ReadIntElement("AfterWrongEvidencePresentedIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SkipWrongEvidencePresentedAction action = new SkipWrongEvidencePresentedAction();
            
            action.setAfterWrongEvidencePresentedIndex(this.afterWrongEvidencePresentedIndex + indexOffset);
            
            return action;
        }
    }

    protected class BeginWrongEvidencePresentedAction extends SingleAction
    {
        public BeginWrongEvidencePresentedAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.CachePreviousActionIndex();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginWrongEvidencePresentedAction");
            writer.EndElement();
        }

        private BeginWrongEvidencePresentedAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginWrongEvidencePresentedAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginWrongEvidencePresentedAction action = new BeginWrongEvidencePresentedAction();
            
            return action;
        }
    }

    protected class EndWrongEvidencePresentedAction extends SingleAction
    {
        public EndWrongEvidencePresentedAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.RestoreActionIndexFromCache();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndWrongEvidencePresentedAction");
            writer.EndElement();
        }

        private EndWrongEvidencePresentedAction(CaseXmlReader reader)
        {
            reader.StartElement("EndWrongEvidencePresentedAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndWrongEvidencePresentedAction action = new EndWrongEvidencePresentedAction();
            
            return action;
        }
    }

    protected class SkipEndRequestedAction extends SingleAction
    {
        public SkipEndRequestedAction()
        {
            this.afterEndRequestedIndex = -1;
        }

        private int afterEndRequestedIndex;

        public int getAfterEndRequestedIndex() { return this.afterEndRequestedIndex; }
        public void setAfterEndRequestedIndex(int afterEndRequestedIndex) { this.afterEndRequestedIndex = afterEndRequestedIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getAfterEndRequestedIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SkipEndRequestedAction");
            writer.WriteIntElement("AfterEndRequestedIndex", this.afterEndRequestedIndex);
            writer.EndElement();
        }

        private SkipEndRequestedAction(CaseXmlReader reader)
        {
            reader.StartElement("SkipEndRequestedAction");
            this.afterEndRequestedIndex = reader.ReadIntElement("AfterEndRequestedIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SkipEndRequestedAction action = new SkipEndRequestedAction();
            
            action.setAfterEndRequestedIndex(this.afterEndRequestedIndex + indexOffset);
            
            return action;
        }
    }

    protected class BeginEndRequestedAction extends SingleAction
    {
        public BeginEndRequestedAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginEndRequestedAction");
            writer.EndElement();
        }

        private BeginEndRequestedAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginEndRequestedAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginEndRequestedAction action = new BeginEndRequestedAction();
            
            return action;
        }
    }

    protected class EndEndRequestedAction extends SingleAction
    {
        public EndEndRequestedAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.setEndRequested(true);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndEndRequestedAction");
            writer.EndElement();
        }

        private EndEndRequestedAction(CaseXmlReader reader)
        {
            reader.StartElement("EndEndRequestedAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndEndRequestedAction action = new EndEndRequestedAction();
            
            return action;
        }
    }

    protected class EndMustPresentEvidenceAction extends SingleAction
    {
        public EndMustPresentEvidenceAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndMustPresentEvidenceAction");
            writer.EndElement();
        }

        private EndMustPresentEvidenceAction(CaseXmlReader reader)
        {
            reader.StartElement("EndMustPresentEvidenceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndMustPresentEvidenceAction action = new EndMustPresentEvidenceAction();
            
            return action;
        }
    }

    protected class EnableConversationAction extends SingleAction
    {
        private String conversationId;

        public EnableConversationAction(String conversationId)
        {
            this.conversationId = conversationId;
        }

        @Override
        public void Execute(State state)
        {
            Conversation conversation = Case.getInstance().getContent().GetConversationFromId(this.conversationId);

            if (!conversation.getIsEnabled())
            {
                conversation.setIsEnabled(true);
            }
            else
            {
                // If the conversation has already been enabled, then we want to
                // skip the notification action.
                state.setActionIndex(state.getActionIndex() + 2);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EnableConversationAction");
            writer.WriteTextElement("ConversationId", this.conversationId);
            writer.EndElement();
        }

        private EnableConversationAction(CaseXmlReader reader)
        {
            reader.StartElement("EnableConversationAction");
            this.conversationId = reader.ReadTextElement("ConversationId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EnableConversationAction action = new EnableConversationAction(this.conversationId);
            
            return action;
        }
    }

    protected class EnableEvidenceAction extends SingleAction
    {
        private String evidenceId;

        public EnableEvidenceAction(String evidenceId)
        {
            this.evidenceId = evidenceId;
        }

        @Override
        public void Execute(State state)
        {
            if (!Case.getInstance().getEvidenceManager().IsEvidenceWithIdEnabled(evidenceId))
            {
                Case.getInstance().getEvidenceManager().EnableEvidenceWithId(evidenceId);
            }
            else
            {
                // If the evidence has already been enabled, then we want to
                // skip the notification action.
                state.setActionIndex(state.getActionIndex() + 2);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EnableEvidenceAction");
            writer.WriteTextElement("EvidenceId", this.evidenceId);
            writer.EndElement();
        }

        private EnableEvidenceAction(CaseXmlReader reader)
        {
            reader.StartElement("EnableEvidenceAction");
            this.evidenceId = reader.ReadTextElement("EvidenceId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EnableEvidenceAction action = new EnableEvidenceAction(this.evidenceId);
            
            return action;
        }
    }

    protected class UpdateEvidenceAction extends SingleAction
    {
        private String evidenceId;
        private String newEvidenceId;

        public UpdateEvidenceAction(String evidenceId, String newEvidenceId)
        {
            this.evidenceId = evidenceId;
            this.newEvidenceId = newEvidenceId;
        }

        @Override
        public void Execute(State state)
        {
            if (Case.getInstance().getEvidenceManager().IsEvidenceWithIdEnabled(evidenceId) &&
                !Case.getInstance().getEvidenceManager().IsEvidenceWithIdEnabled(newEvidenceId))
            {
                Case.getInstance().getEvidenceManager().DisableEvidenceWithId(evidenceId);
                Case.getInstance().getEvidenceManager().EnableEvidenceWithId(newEvidenceId);
            }
            else
            {
                // If the evidence has already been updated,
                // or if we don't have the initial evidence in the first place,
                // then we want to skip the notification action.
                state.setActionIndex(state.getActionIndex() + 2);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("UpdateEvidenceAction");
            writer.WriteTextElement("EvidenceId", this.evidenceId);
            writer.WriteTextElement("NewEvidenceId", this.newEvidenceId);
            writer.EndElement();
        }

        private UpdateEvidenceAction(CaseXmlReader reader)
        {
            reader.StartElement("UpdateEvidenceAction");
            this.evidenceId = reader.ReadTextElement("EvidenceId");
            this.newEvidenceId = reader.ReadTextElement("NewEvidenceId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            UpdateEvidenceAction action = new UpdateEvidenceAction(this.evidenceId, this.newEvidenceId);
            
            return action;
        }
    }

    protected class DisableEvidenceAction extends SingleAction
    {
        private String evidenceId;

        public DisableEvidenceAction(String evidenceId)
        {
            this.evidenceId = evidenceId;
        }

        @Override
        public void Execute(State state)
        {
            if (Case.getInstance().getEvidenceManager().IsEvidenceWithIdEnabled(evidenceId))
            {
                Case.getInstance().getEvidenceManager().DisableEvidenceWithId(evidenceId);
            }
            else
            {
                // If the evidence has already been disabled,
                // or if we don't have the initial evidence in the first place,
                // then we want to skip the notification action.
                state.setActionIndex(state.getActionIndex() + 2);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("DisableEvidenceAction");
            writer.WriteTextElement("EvidenceId", this.evidenceId);
            writer.EndElement();
        }

        private DisableEvidenceAction(CaseXmlReader reader)
        {
            reader.StartElement("DisableEvidenceAction");
            this.evidenceId = reader.ReadTextElement("EvidenceId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            DisableEvidenceAction action = new DisableEvidenceAction(this.evidenceId);
            
            return action;
        }
    }

    protected class EnableCutsceneAction extends SingleAction
    {
        private String cutsceneId;

        public EnableCutsceneAction(String cutsceneId)
        {
            this.cutsceneId = cutsceneId;
        }

        @Override
        public void Execute(State state)
        {
            if (!Case.getInstance().getFieldCutsceneManager().GetCutsceneFromId(this.cutsceneId).getIsEnabled())
            {
                Case.getInstance().getFieldCutsceneManager().GetCutsceneFromId(this.cutsceneId).setIsEnabled(true);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EnableCutsceneAction");
            writer.WriteTextElement("CutsceneId", this.cutsceneId);
            writer.EndElement();
        }

        private EnableCutsceneAction(CaseXmlReader reader)
        {
            reader.StartElement("EnableCutsceneAction");
            this.cutsceneId = reader.ReadTextElement("CutsceneId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EnableCutsceneAction action = new EnableCutsceneAction(this.cutsceneId);
            
            return action;
        }
    }

    public class NotificationAction extends ContinuousAction
    {
        private Notification notification;

        public NotificationAction()
        {
        }

        public NotificationAction(String partnerId)
        {
            this.partnerId = partnerId;
        }

        public NotificationAction(String oldEvidenceId, String newEvidenceId)
        {
            this.oldEvidenceId = oldEvidenceId;
            this.newEvidenceId = newEvidenceId;
        }

        private String rawNotificationText;

        public String getRawNotificationText() { return this.rawNotificationText; }
        public void setRawNotificationText(String rawNotificationText) { this.rawNotificationText = rawNotificationText; }

        private String partnerId;

        public String getPartnerId() { return this.partnerId; }
        private void setPartnerId(String partnerId) { this.partnerId = partnerId; }

        private String oldEvidenceId;

        public String getOldEvidenceId() { return this.oldEvidenceId; }
        private void setOldEvidenceId(String oldEvidenceId) { this.oldEvidenceId = oldEvidenceId; }

        private String newEvidenceId;

        public String getNewEvidenceId() { return this.newEvidenceId; }
        private void setNewEvidenceId(String newEvidenceId) { this.newEvidenceId = newEvidenceId; }

        @Override
        public void Begin(State state) throws CaseParseException
        {
            super.Begin(state);

            if (this.notification == null)
            {
                this.notification = new Notification(this.getRawNotificationText(), this.getPartnerId());
            }

            this.notification.Begin();
        }

        @Override
        public void Update(GameContainer gc,int delta)
        {
            this.notification.Update(gc,delta);

            if (this.notification.getIsFinished())
            {
                this.setIsFinished(true);
            }
        }

        @Override
        public void Draw(GameContainer gc, Graphics g, float yOffset)
        {
            this.notification.Draw(gc, g, yOffset);
        }

        @Override
        public void Reset()
        {
            super.Reset();

            if (this.notification != null)
            {
                this.notification.Reset();
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("NotificationAction");
            writer.WriteTextElement("RawNotificationText", this.rawNotificationText);
            
            if (this.partnerId != null)
            {
                writer.WriteTextElement("PartnerId", this.partnerId);
            }
            
            if (this.oldEvidenceId != null)
            {
                writer.WriteTextElement("OldEvidenceId", this.oldEvidenceId);
            }
            
            if (this.newEvidenceId != null)
            {
                writer.WriteTextElement("NewEvidenceId", this.newEvidenceId);
            }
            
            writer.EndElement();
        }

        public NotificationAction(CaseXmlReader reader)
        {
            reader.StartElement("NotificationAction");
            this.rawNotificationText = reader.ReadTextElement("RawNotificationText");
            
            if (reader.ElementExists("PartnerId"))
            {
                this.partnerId = reader.ReadTextElement("PartnerId");
            }
            
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            return this;
        }
    }

    protected class PlayBgmAction extends SingleAction
    {
        private String bgmId;
        private boolean preserveBgm;

        public PlayBgmAction(String bgmId, boolean preserveBgm)
        {
            this.bgmId = bgmId;
            this.preserveBgm = preserveBgm;
            
            Case.getInstance().getAudioManager().RequestBgm(bgmId);
        }

        @Override
        public void Execute(State state)
        {
            Case.getInstance().getAudioManager().PlayBgmWithId(bgmId);
            state.setPreserveBgm(this.preserveBgm);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("PlayBgmAction");
            writer.WriteTextElement("BgmId", this.bgmId);
            writer.WriteBooleanElement("PreserveBgm", this.preserveBgm);
            writer.EndElement();
        }

        private PlayBgmAction(CaseXmlReader reader)
        {
            reader.StartElement("PlayBgmAction");
            this.bgmId = reader.ReadTextElement("BgmId");
            this.preserveBgm = reader.ReadBooleanElement("PreserveBgm");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            PlayBgmAction action = new PlayBgmAction(this.bgmId, this.preserveBgm);
            
            return action;
        }
    }

    protected class PauseBgmAction extends SingleAction
    {
        public PauseBgmAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            Case.getInstance().getAudioManager().PauseCurrentBgm();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("PauseBgmAction");
            writer.EndElement();
        }

        private PauseBgmAction(CaseXmlReader reader)
        {
            reader.StartElement("PauseBgmAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            PauseBgmAction action = new PauseBgmAction();
            
            return action;
        }
    }

    protected class ResumeBgmAction extends SingleAction
    {
        public ResumeBgmAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            Case.getInstance().getAudioManager().ResumeCurrentBgm();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ResumeBgmAction");
            writer.EndElement();
        }

        private ResumeBgmAction(CaseXmlReader reader)
        {
            reader.StartElement("ResumeBgmAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            ResumeBgmAction action = new ResumeBgmAction();
            
            return action;
        }
    }

    protected class StopBgmAction extends SingleAction
    {
        private boolean isInstant;
        private boolean preserveBgm;
        
        public StopBgmAction(boolean isInstant)
        {
            this.isInstant = isInstant;
            this.preserveBgm = false;
        }
        
        public StopBgmAction(boolean isInstant, boolean preserveBgm)
        {
            this.isInstant = isInstant;
            this.preserveBgm = preserveBgm;
        }

        @Override
        public void Execute(State state)
        {
            Case.getInstance().getAudioManager().StopCurrentBgm();
            state.setPreserveBgm(this.preserveBgm);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("StopBgmAction");
            writer.WriteBooleanElement("IsInstant", this.isInstant);
            writer.WriteBooleanElement("PreserveBgm", this.preserveBgm);
            writer.EndElement();
        }

        private StopBgmAction(CaseXmlReader reader)
        {
            reader.StartElement("StopBgmAction");
            this.isInstant = reader.ReadBooleanElement("IsInstant");
            this.preserveBgm = reader.ReadBooleanElement("PreserveBgm");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            StopBgmAction action = new StopBgmAction(this.isInstant, this.preserveBgm);
            
            return action;
        }
    }

    protected class PlayAmbianceAction extends SingleAction
    {
        private String ambianceSfxId;
        private boolean preserveAmbiance;

        public PlayAmbianceAction(String ambianceSfxId, boolean preserveAmbiance)
        {
            this.ambianceSfxId = ambianceSfxId;
            this.preserveAmbiance = preserveAmbiance;
            
            Case.getInstance().getAudioManager().RequestSfx(this.ambianceSfxId);
        }

        @Override
        public void Execute(State state)
        {
            //Case.getInstance().getAudioManager().PlayAmbianceWithId(this.ambianceSfxId);
            //state.setPreserveAmbiance(this.preserveAmbiance);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("PlayAmbianceAction");
            writer.WriteTextElement("AmbianceSfxId", this.ambianceSfxId);
            writer.WriteBooleanElement("PreserveAmbiance", this.preserveAmbiance);
            writer.EndElement();
        }

        private PlayAmbianceAction(CaseXmlReader reader)
        {
            reader.StartElement("PlayAmbianceAction");
            this.ambianceSfxId = reader.ReadTextElement("AmbianceSfxId");
            this.preserveAmbiance = reader.ReadBooleanElement("PreserveAmbiance");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            PlayAmbianceAction action = new PlayAmbianceAction(this.ambianceSfxId, this.preserveAmbiance);
            
            return action;
        }
    }

    protected class PauseAmbianceAction extends SingleAction
    {
        public PauseAmbianceAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            //Case.getInstance().getAudioManager().PauseCurrentAmbiance();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("PauseAmbianceAction");
            writer.EndElement();
        }

        private PauseAmbianceAction(CaseXmlReader reader)
        {
            reader.StartElement("PauseAmbianceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            PauseAmbianceAction action = new PauseAmbianceAction();
            
            return action;
        }
    }

    protected class ResumeAmbianceAction extends SingleAction
    {
        public ResumeAmbianceAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            //Case.getInstance().getAudioManager().ResumeCurrentAmbiance();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ResumeAmbianceAction");
            writer.EndElement();
        }

        private ResumeAmbianceAction(CaseXmlReader reader)
        {
            reader.StartElement("ResumeAmbianceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            ResumeAmbianceAction action = new ResumeAmbianceAction();
            
            return action;
        }
    }

    protected class StopAmbianceAction extends SingleAction
    {
        private boolean isInstant;
        private boolean preserveAmbiance;
        
        public StopAmbianceAction(boolean isInstant)
        {
            this.isInstant = isInstant;
            this.preserveAmbiance = false;
        }
        
        public StopAmbianceAction(boolean isInstant, boolean preserveAmbiance)
        {
            this.isInstant = isInstant;
            this.preserveAmbiance = preserveAmbiance;
        }

        @Override
        public void Execute(State state)
        {
            //Case.getInstance().getAudioManager().StopCurrentAmbiance();
            //state.setPreserveAmbiance(this.preserveAmbiance);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("StopAmbianceAction");
            writer.WriteBooleanElement("IsInstant", this.isInstant);
            writer.WriteBooleanElement("PreserveAmbiance", this.preserveAmbiance);
            writer.EndElement();
        }

        private StopAmbianceAction(CaseXmlReader reader)
        {
            reader.StartElement("StopAmbianceAction");
            this.isInstant = reader.ReadBooleanElement("IsInstant");
            this.preserveAmbiance = reader.ReadBooleanElement("PreserveAmbiance");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            StopAmbianceAction action = new StopAmbianceAction(this.isInstant, this.preserveAmbiance);
            
            return action;
        }
    }

    protected class StartAnimationAction extends SingleAction
    {
        private String animationId;

        public StartAnimationAction(String animationId)
        {
            this.animationId = animationId;
        }

        @Override
        public void Execute(State state)
        {
            state.setCurrentAnimation(Case.getInstance().getDialogCutsceneManager().GetAnimationFromId(this.animationId));
            state.getCurrentAnimation().Begin();
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("StartAnimationAction");
            writer.WriteTextElement("AnimationId", this.animationId);
            writer.EndElement();
        }

        private StartAnimationAction(CaseXmlReader reader)
        {
            reader.StartElement("StartAnimationAction");
            this.animationId = reader.ReadTextElement("AnimationId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            StartAnimationAction action = new StartAnimationAction(this.animationId);
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
            Case.getInstance().getDialogCutsceneManager().GetAnimationFromId(this.animationId).markChildSprites(markParent);
        }
    }

    protected class StopAnimationAction extends SingleAction
    {
        public StopAnimationAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.getCurrentAnimation().Reset();
            state.setCurrentAnimation(null);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("StopAnimationAction");
            writer.EndElement();
        }

        private StopAnimationAction(CaseXmlReader reader)
        {
            reader.StartElement("StopAnimationAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            StopAnimationAction action = new StopAnimationAction();
            
            return action;
        }
    }

    protected class BeginCheckPartnerAction extends SingleAction
    {
        private String partnerId;

        public BeginCheckPartnerAction(String partnerId)
        {
            this.partnerId = partnerId;
        }

        // Action index to branch to if the right partner is present.
        private int rightIndex;

        public int getRightIndex() { return this.rightIndex; }
        public void setRightIndex(int rightIndex) { this.rightIndex = rightIndex; }

        // Action index to branch to if the right partner is not present.
        private int wrongIndex;

        public int getWrongIndex() { return this.wrongIndex; }
        public void setWrongIndex(int wrongIndex) { this.wrongIndex = wrongIndex; }

        @Override
        public void Execute(State state)
        {
            if (this.partnerId == null ||
                (Utils.equals(Case.getInstance().getPartnerManager().GetCurrentPartnerId(), this.partnerId)))
            {
                state.setActionIndex(this.getRightIndex());
            }
            else if (this.partnerId != null)
            {
                state.setActionIndex(this.getWrongIndex());
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginCheckPartnerAction");
            writer.WriteTextElement("PartnerId", this.partnerId);
            writer.WriteIntElement("RightIndex", this.rightIndex);
            writer.WriteIntElement("WrongIndex", this.wrongIndex);
            writer.EndElement();
        }

        private BeginCheckPartnerAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginCheckPartnerAction");
            this.partnerId = reader.ReadTextElement("PartnerId");
            this.rightIndex = reader.ReadIntElement("RightIndex");
            this.wrongIndex = reader.ReadIntElement("WrongIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginCheckPartnerAction action = new BeginCheckPartnerAction(this.partnerId);
            
            action.setRightIndex(this.rightIndex + indexOffset);
            action.setWrongIndex(this.wrongIndex + indexOffset);
            
            return action;
        }
    }

    // Dummy placeholder to jump to when the right partner is present.
    protected class BranchIfRightAction extends SingleAction
    {
        public BranchIfRightAction()
        {
            this.endIndex = -1;
        }

        // Action index to branch to representing the end of this branch statement.
        private int endIndex;

        public int getEndIndex() { return this.endIndex; }
        public void setEndIndex(int endIndex) { this.endIndex = endIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getEndIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BranchIfRightAction");
            writer.WriteIntElement("EndIndex", this.endIndex);
            writer.EndElement();
        }

        private BranchIfRightAction(CaseXmlReader reader)
        {
            reader.StartElement("BranchIfRightAction");
            this.endIndex = reader.ReadIntElement("EndIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BranchIfRightAction action = new BranchIfRightAction();
            
            action.setEndIndex(this.endIndex + indexOffset);
            
            return action;
        }
    }

    // Dummy placeholder to jump to when the right partner is not present.
    protected class BranchIfWrongAction extends SingleAction
    {
        public BranchIfWrongAction()
        {
            this.endIndex = -1;
        }

        // Action index to branch to representing the end of this branch statement.
        private int endIndex;

        public int getEndIndex() { return this.endIndex; }
        public void setEndIndex(int endIndex) { this.endIndex = endIndex; }

        @Override
        public void Execute(State state)
        {
            state.setActionIndex(this.getEndIndex());
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BranchIfWrongAction");
            writer.WriteIntElement("EndIndex", this.endIndex);
            writer.EndElement();
        }

        private BranchIfWrongAction(CaseXmlReader reader)
        {
            reader.StartElement("BranchIfWrongAction");
            this.endIndex = reader.ReadIntElement("EndIndex");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BranchIfWrongAction action = new BranchIfWrongAction();
            
            action.setEndIndex(this.endIndex + indexOffset);
            
            return action;
        }
    }

    // Dummy placeholder to jump to when finished with a check partner action.
    protected class EndCheckPartnerAction extends SingleAction
    {
        public EndCheckPartnerAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndCheckPartnerAction");
            writer.EndElement();
        }

        private EndCheckPartnerAction(CaseXmlReader reader)
        {
            reader.StartElement("EndCheckPartnerAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndCheckPartnerAction action = new EndCheckPartnerAction();
            
            return action;
        }
    }

    // Sets the current partner to the partner designated by the given ID.
    protected class SetPartnerAction extends SingleAction
    {
        private String partnerId;

        public SetPartnerAction(String partnerId)
        {
            this.partnerId = partnerId;
        }

        @Override
        public void Execute(State state)
        {
            if (!Utils.equals(Case.getInstance().getPartnerManager().GetCurrentPartnerId(), this.partnerId))
            {
                Case.getInstance().getPartnerManager().SetCurrentPartner(this.partnerId);
            }
            else
            {
                // If this partner is already the current partner, then we want to
                // skip the notification action.
                state.setActionIndex(state.getActionIndex() + 2);
            }
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("SetPartnerAction");
            writer.WriteTextElement("PartnerId", this.partnerId);
            writer.EndElement();
        }

        private SetPartnerAction(CaseXmlReader reader)
        {
            reader.StartElement("SetPartnerAction");
            this.partnerId = reader.ReadTextElement("PartnerId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            SetPartnerAction action = new SetPartnerAction(this.partnerId);
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
            if (this.partnerId.equals("None"))
            {
                return;
            }
            
            FieldCharacter fieldCharacter = Case.getInstance().getFieldCharacterManager().GetCharacterFromId(this.partnerId);
            
            for (FieldCharacterState fieldCharacterState : FieldCharacterState.values())
            {
                for (FieldCharacterDirection direction : FieldCharacterDirection.values())
                {
                    fieldCharacter.markChildSprites(markParent, fieldCharacterState, direction);
                }
            }
            
            Partner partner = Case.getInstance().getPartnerManager().GetPartnerFromId(this.partnerId);
            partner.markChildSprites(markParent);
        }
    }

    protected class GoToPresentWrongEvidenceAction extends SingleAction
    {
        public GoToPresentWrongEvidenceAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.setPresentWrongEvidenceRequested(true);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("GoToPresentWrongEvidenceAction");
            writer.EndElement();
        }

        private GoToPresentWrongEvidenceAction(CaseXmlReader reader)
        {
            reader.StartElement("GoToPresentWrongEvidenceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            GoToPresentWrongEvidenceAction action = new GoToPresentWrongEvidenceAction();
            
            return action;
        }
    }

    protected class LockConversationAction extends SingleAction
    {
        private UnlockCondition unlockCondition;
        
        public LockConversationAction(String unlockCondition, String unlockConditionParameter) throws CaseParseException
        {
            if (Utils.equals(unlockCondition, "Flag"))
            {
                this.unlockCondition = new FlagSetUnlockCondition(unlockConditionParameter);
            }
            else if (Utils.equals(unlockCondition, "Partner"))
            {
                this.unlockCondition = new PartnerPresentUnlockCondition(unlockConditionParameter);
            }
            else
            {
                throw new CaseParseException("Unknown unlock condition \"" + unlockCondition + "\".");
            }
        }
        
        private LockConversationAction(UnlockCondition unlockCondition)
        {
            this.unlockCondition = unlockCondition.Clone();
        }

        @Override
        public void Execute(State state)
        {
            //state.getCurrentConversation().getUnlockConditions().add(this.unlockCondition);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("LockConversationAction");
            
            writer.StartElement("UnlockCondition");
            unlockCondition.SaveToXml(writer);
            writer.EndElement();
            
            writer.EndElement();
        }

        private LockConversationAction(CaseXmlReader reader)
        {
            reader.StartElement("LockConversationAction");
            //this.unlockCondition = reader.ReadTextElement("UnlockCondition");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            LockConversationAction action = new LockConversationAction(this.unlockCondition);
            
            return action;
        }
        
        private abstract class UnlockCondition
        {
            public abstract boolean GetIsConditionMet();
            public abstract void SaveToXml(CaseXmlWriter writer) throws IOException;
            public abstract UnlockCondition Clone();
        }
        
        private class FlagSetUnlockCondition extends UnlockCondition
        {
            String flagId;
            
            public FlagSetUnlockCondition(String flagId)
            {
                this.flagId = flagId;
            }
            
            @Override
            public boolean GetIsConditionMet()
            {
                return Case.getInstance().getFlagManager().IsFlagSet(this.flagId);
            }

            @Override
            public void SaveToXml(CaseXmlWriter writer) throws IOException
            {
                writer.StartElement("FlagSetUnlockCondition");
                writer.WriteTextElement("FlagId", this.flagId);
                writer.EndElement();
            }

            @Override
            public UnlockCondition Clone()
            {
                return new FlagSetUnlockCondition(this.flagId);
            }
        }
        
        private class PartnerPresentUnlockCondition extends UnlockCondition
        {
            String partnerId;
            
            public PartnerPresentUnlockCondition(String partnerId)
            {
                this.partnerId = partnerId;
            }
            
            @Override
            public boolean GetIsConditionMet()
            {
                return Case.getInstance().getPartnerManager().GetCurrentPartnerId().equals(this.partnerId);
            }

            @Override
            public void SaveToXml(CaseXmlWriter writer) throws IOException
            {
                writer.StartElement("PartnerPresentUnlockCondition");
                writer.WriteTextElement("PartnerId", this.partnerId);
                writer.EndElement();
            }

            @Override
            public UnlockCondition Clone()
            {
                return new PartnerPresentUnlockCondition(this.partnerId);
            }
        }
    }

    protected class ExitEncounterAction extends SingleAction
    {
        public ExitEncounterAction()
        {
        }

        @Override
        public void Execute(State state)
        {
            state.setEndRequested(true);
            state.setExitRequested(true);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ExitEncounterAction");
            writer.EndElement();
        }

        private ExitEncounterAction(CaseXmlReader reader)
        {
            reader.StartElement("ExitEncounterAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            ExitEncounterAction action = new ExitEncounterAction();
            
            return action;
        }
    }

    protected class MoveToLocationAction extends SingleAction
    {
        private String newAreaId;
        private String newLocationId;
        private String transitionId;
        
        public MoveToLocationAction(String newAreaId, String newLocationId, String transitionId)
        {
            this.newAreaId = newAreaId;
            this.newLocationId = newLocationId;
            this.transitionId = transitionId;
        }

        @Override
        public void Execute(State state)
        {
            state.setNewAreaRequested(this.newAreaId);
            state.setNewLocationRequested(this.newLocationId);
            state.setTransitionId(this.transitionId);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("MoveToLocationAction");
            writer.WriteTextElement("NewAreaId", this.newAreaId);
            writer.WriteTextElement("NewLocationId", this.newLocationId);
            writer.WriteTextElement("TransitionId", this.transitionId);
            writer.EndElement();
        }

        private MoveToLocationAction(CaseXmlReader reader)
        {
            reader.StartElement("MoveToLocationAction");
            this.newAreaId = reader.ReadTextElement("NewAreaId");
            this.newLocationId = reader.ReadTextElement("NewLocationId");
            this.transitionId = reader.ReadTextElement("TransitionId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            MoveToLocationAction action = new MoveToLocationAction(this.newAreaId, this.newLocationId, this.transitionId);
            
            return action;
        }
    }

    protected class MoveToZoomedViewAction extends SingleAction
    {
        private String zoomedViewId;
        
        public MoveToZoomedViewAction(String zoomedViewId)
        {
            this.zoomedViewId = zoomedViewId;
        }

        @Override
        public void Execute(State state)
        {
            //state.setNewAreaRequested(this.newAreaId);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("MoveToZoomedViewAction");
            writer.WriteTextElement("ZoomedViewId", this.zoomedViewId);
            writer.EndElement();
        }

        private MoveToZoomedViewAction(CaseXmlReader reader)
        {
            reader.StartElement("MoveToZoomedViewAction");
            this.zoomedViewId = reader.ReadTextElement("ZoomedViewId");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            MoveToZoomedViewAction action = new MoveToZoomedViewAction(this.zoomedViewId);
            
            return action;
        }
    }

    protected class EndCaseAction extends SingleAction
    {
        private boolean completesCase;
        
        public EndCaseAction(boolean completesCase)
        {
            this.completesCase = completesCase;
        }

        @Override
        public void Execute(State state)
        {
            Case.getInstance().setIsFinished(true);
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndCaseAction");
            writer.WriteBooleanElement("CompletesCase", this.completesCase);
            writer.EndElement();
        }

        private EndCaseAction(CaseXmlReader reader)
        {
            reader.StartElement("EndCaseAction");
            this.completesCase = reader.ReadBooleanElement("CompletesCase");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndCaseAction action = new EndCaseAction(this.completesCase);
            
            return action;
        }
    }

    protected class BeginMultipleChoiceAction extends SingleAction
    {
        private ArrayList<String> optionTexts;
        private ArrayList<Integer> optionIndexes;
    
        public BeginMultipleChoiceAction()
        {
            this.optionTexts = new ArrayList<String>();
            this.optionIndexes = new ArrayList<Integer>();
        }
        
        public void AddOption(String text, int index)
        {
            this.optionTexts.add(text);
            this.optionIndexes.add(index);
        }
        
        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginMultipleChoiceAction");
            
            writer.StartElement("OptionTexts");
            
            for (String s : this.optionTexts)
            {
                writer.StartElement("Entry");
                writer.WriteTextElement("Text", s);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.StartElement("OptionIndexes");
            
            for (Integer i : this.optionIndexes)
            {
                writer.StartElement("Entry");
                writer.WriteIntElement("Index", i);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.EndElement();
        }

        private BeginMultipleChoiceAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginMultipleChoiceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginMultipleChoiceAction action = new BeginMultipleChoiceAction();
            
            for (int i = 0; i < this.optionTexts.size(); i++)
            {
                action.AddOption(this.optionTexts.get(i), this.optionIndexes.get(i) + indexOffset);
            }
            
            return action;
        }
    }

    protected class BeginMultipleChoiceOptionAction extends SingleAction
    {
        public BeginMultipleChoiceOptionAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginMultipleChoiceOptionAction");
            writer.EndElement();
        }

        private BeginMultipleChoiceOptionAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginMultipleChoiceOptionAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginMultipleChoiceOptionAction action = new BeginMultipleChoiceOptionAction();
            
            return action;
        }
    }

    protected class ExitMultipleChoiceAction extends SingleAction
    {
        private int exitIndex;
        
        public ExitMultipleChoiceAction()
        {
        }
        
        public void setExitIndex(int exitIndex) { this.exitIndex = exitIndex; }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("ExitMultipleChoiceAction");
            writer.WriteIntElement("ExitIndex", this.exitIndex);
            writer.EndElement();
        }

        private ExitMultipleChoiceAction(CaseXmlReader reader)
        {
            reader.StartElement("ExitMultipleChoiceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            ExitMultipleChoiceAction action = new ExitMultipleChoiceAction();
            
            action.setExitIndex(this.exitIndex + indexOffset);
            
            return action;
        }
    }

    protected class EndMultipleChoiceOptionAction extends SingleAction
    {
        private int startIndex;
        
        public EndMultipleChoiceOptionAction(int startIndex)
        {
            this.startIndex = startIndex;
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndMultipleChoiceOptionAction");
            writer.WriteIntElement("StartIndex", this.startIndex);
            writer.EndElement();
        }

        private EndMultipleChoiceOptionAction(CaseXmlReader reader)
        {
            reader.StartElement("EndMultipleChoiceOptionAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndMultipleChoiceOptionAction action = new EndMultipleChoiceOptionAction(this.startIndex + indexOffset);
            
            return action;
        }
    }

    protected class EndMultipleChoiceAction extends SingleAction
    {
        public EndMultipleChoiceAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndMultipleChoiceAction");
            writer.EndElement();
        }

        private EndMultipleChoiceAction(CaseXmlReader reader)
        {
            reader.StartElement("EndMultipleChoiceAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndMultipleChoiceAction action = new EndMultipleChoiceAction();
            
            return action;
        }
    }

    protected class EnableFastForwardAction extends SingleAction
    {
        public EnableFastForwardAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EnableFastForwardAction");
            writer.EndElement();
        }

        private EnableFastForwardAction(CaseXmlReader reader)
        {
            reader.StartElement("EnableFastForwardAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EnableFastForwardAction action = new EnableFastForwardAction();
            
            return action;
        }
    }

    protected class DisableFastForwardAction extends SingleAction
    {
        public DisableFastForwardAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("DisableFastForwardAction");
            writer.EndElement();
        }

        private DisableFastForwardAction(CaseXmlReader reader)
        {
            reader.StartElement("DisableFastForwardAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            DisableFastForwardAction action = new DisableFastForwardAction();
            
            return action;
        }
    }

    protected class BeginBreakdownAction extends SingleAction
    {
        CharacterPosition characterPosition;
        
        public BeginBreakdownAction(CharacterPosition characterPosition)
        {
            this.characterPosition = characterPosition;
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("BeginBreakdownAction");
            writer.WriteTextElement("CharacterPosition", this.characterPosition.name());
            writer.EndElement();
        }

        private BeginBreakdownAction(CaseXmlReader reader)
        {
            reader.StartElement("BeginBreakdownAction");
            this.characterPosition = CharacterPosition.valueOf(reader.ReadTextElement("CharacterId"));
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            BeginBreakdownAction action = new BeginBreakdownAction(this.characterPosition);
            
            return action;
        }
        
        @Override
        protected void markChildSprites(String markParent, ActionState state) throws CaseParseException
        {
            String characterId = null;
            
            if (characterPosition != CharacterPosition.Left &&
                characterPosition != CharacterPosition.Right)
            {
                throw new CaseParseException("BeginBreakdown must point to either Left or Right.");
            }
            
            if (this.characterPosition == CharacterPosition.Left)
            {
                characterId = state.getLeftCharacterId();
            }
            else
            {
                characterId = state.getRightCharacterId();
            }
            
            DialogCharacter d = Case.getInstance().getDialogCharacterManager().GetCharacterFromId(characterId);
            d.markBreakdownChildSprites(markParent);
        }
    }

    protected class EndBreakdownAction extends SingleAction
    {
        public EndBreakdownAction()
        {
        }

        @Override
        public void Execute(State state)
        {
        }
        
        @Override
        public void SaveToXml(CaseXmlWriter writer) throws IOException
        {
            writer.StartElement("EndBreakdownAction");
            writer.EndElement();
        }

        private EndBreakdownAction(CaseXmlReader reader)
        {
            reader.StartElement("EndBreakdownAction");
            reader.EndElement();
        }
        
        protected Action CloneWithOffset(int indexOffset)
        {
            EndBreakdownAction action = new EndBreakdownAction();
            
            return action;
        }
    }
}
